Gam­ma-Mess­ge­rät – Inbetriebnahme

Ein­lei­tung

Der im vori­gen Bei­trag bereits ange­kün­dig­te RTL-Code für das FPGA und die zuge­hö­ri­ge Firm­ware sind jetzt funk­ti­ons­fä­hig und soweit, daß man bei­des vor­zei­gen kann. Es ist noch der eine oder ande­re Test­code ent­hal­ten, daher ist bei­des noch nicht als „Release“ gekenn­zeich­net. Außer­dem soll es noch ein paar Ände­run­gen in der näch­sten Revi­si­on der Lei­ter­plat­te geben.

Nach ein paar grund­le­gen­den Bemer­kun­gen zum Mess­prin­zip wird nach­fol­gend zuerst der Ver­i­log-Code beschrie­ben, anschlie­ßend die Soft­ware und zum Abschluss wer­den ein paar Bei­spiel­mes­sun­gen mit einem Signal­ge­nera­tor gezeigt. Am Ende des Bei­trags fol­gen noch ein paar Anmer­kun­gen zum PicoRV32-Risc-V-Core.

Das ver­wen­de­te Tool zum Erstel­len des FPGAs ist der Gowin FPGA Desi­gner V1.9.11.01. Für die Soft­ware­er­stel­lung wird GMD (Gowin for Embedded C/C++ Deve­lo­pers), Ver­si­on: 2025.01 verwendet.

Als Refe­renz soll hier zunächst noch­mal das Block­dia­gramm aus dem vori­gen Bei­trag gezeigt wer­den, weil sich die nach­fol­gen­den Erklä­run­gen dar­auf beziehen:

Das Mess­prin­zip

Die vor- und rück­lau­fen­den Ana­log­si­gna­le wer­den in den Kom­pa­ra­to­ren digi­ta­li­siert und anschlie­ßend im FPGA digi­tal aus­ge­wer­tet. Hier ist exem­pla­risch ein Timing­dia­gramm für ein 10-MHz-Signal und eine Abta­stung mit 200 MHz gezeigt:

Oben wird das vor­lau­fen­de Signal und dar­un­ter das reflek­tier­te und um 90° pha­sen­ver­scho­be­ne rück­lau­fen­de Signal gezeigt. Die drit­te und vier­te Kur­ve zei­gen jeweils das digi­ta­li­sier­te Signal und danach ist das exklu­siv-ver­o­der­te Pha­sen­dif­fe­renz­si­gnal dar­ge­stellt, aus dem die Pha­sen­ver­schie­bung bestimmt wird. Das Abtast­si­gnal hat eine deut­lich höhe­re Fre­quenz, in die­sem Fall 200 MHz.

Ein Mess­in­ter­vall dau­ert typi­scher­wei­se zwi­schen 10 und 100 ms. Wäh­rend die­ses Inter­valls wer­den ver­schie­de­ne Zäh­ler unter bestimm­ten Bedin­gun­gen mit dem Takt des Abtast­si­gnals hoch­ge­zählt und am Ende des Mess­in­ter­valls von der CPU aus­ge­le­sen und aus­ge­wer­tet. Beim Start des näch­sten Mess­in­ter­valls wer­den die­se Zäh­ler auto­ma­tisch wie­der gelöscht und star­ten bei null.

Pha­se Dif­fe­rence Coun­ter PDC

Alle Zäh­ler sind im Modul „Reflection_Measurement“ in der Datei „RefMeas.v“ defi­niert. Der Pha­se Dif­fe­rence Coun­ter PDC wird immer dann inkre­men­tiert, wenn das Pha­sen­dif­fe­renz­si­gnal auf high liegt, wenn also bei­de Ein­gangs­si­gna­le ver­schie­den sind. Am Ende des Mess­in­ter­valls zeigt das Ver­hält­nis des PDC-Zäh­ler­stan­des zur Gesamt­an­zahl der Tak­te wäh­rend des Inter­valls also die Pha­sen­ver­schie­bung zwi­schen den Ein­gangs­si­gna­len an.

Edge Coun­ter EC

Die Pha­sen­mes­sung mit dem Pha­se Dif­fe­rence Coun­ter lie­fert ein zwei­deu­ti­ges Ergeb­nis, denn eine nega­ti­ve Pha­sen­ver­schie­bung führt zu dem­sel­ben Zäh­ler­stand, wie eine posi­ti­ve Ver­schie­bung. Die­se Mehr­deu­tig­keit löst der Edge Coun­ter EC auf. Die­ser Zäh­ler wird dann inkre­men­tiert, wenn das rück­lau­fen­de Signal bei einer stei­gen­den Flan­ke des vor­lau­fen­den Signals low ist. Ein wei­te­rer gleich­ar­ti­ger Zäh­ler wird dann inkre­men­tiert, wenn das vor­lau­fen­de Signal bei einer stei­gen­den Flan­ke des rück­lau­fen­den Signals low ist. Am Ende der Mes­sung trifft die CPU eine Mehr­heits­ent­schei­dung und bestimmt so das Vor­zei­chen der Pha­sen­ver­schie­bung. Bei 0° und 180° kommt es hier zu Unsi­cher­hei­ten, die aber im Rah­men der Mess­ge­nau­ig­keit liegen.

Fre­quen­cy Coun­ter FC1 und FC2

Die bei­den Fre­quenz­zäh­ler FC1 und FC2 wer­den immer dann inkre­men­tiert, wenn eine stei­gen­de Flan­ke auf dem jewei­li­gen Ein­gangs­si­gnal fest­ge­stellt wur­de. Aus dem Zäh­ler­stand am Ende der Mes­sung bestimmt die CPU dann die jewei­li­ge Frequenz.

Die­se Fre­quen­zen sind natur­ge­mäß gleich, weil das rück­lau­fen­de Signal ja vom vor­lau­fen­den Signal abge­lei­tet ist. Bei nahe­zu idea­ler Anpas­sung geht aber der Pegel des reflek­tier­ten Signals immer wei­ter gegen null und die gemes­se­ne Fre­quenz genau­so. Der Ver­gleich die­ser Fre­quen­zen ist daher ein Maß für die Zuver­läs­sig­keit der Pha­sen­mes­sung. Wei­chen sie (deut­lich) von­ein­an­der ab, ist die Pha­sen­mes­sung mit den oben genann­ten Regi­stern ungültig.

High und Low Counter

Im unten vor­ge­stell­ten Design sind noch vier wei­te­re Zäh­ler imple­men­tiert, die im Block­dia­gramm nicht gezeigt sind und zukünf­tig ent­fal­len sol­len: F‑High, F‑Low, R‑High und R‑Low. Die High-Zäh­ler wer­den inkre­men­tiert, wenn das ent­spre­chen­de Ein­gangs­si­gnal bei stei­gen­der Flan­ke des Abtast­tak­tes high ist, die (red­un­dan­ten) Low-Zäh­ler dann, wenn das Signal low ist. Wie im Bei­trag „Gam­­ma-Mes­s­­ge­rät – Nach­trag zu den Mes­sun­gen“ beschrie­ben wur­de, gibt es beim Pro­to­typ einen Off­set in der Ver­gleichs­span­nung der Kom­pa­ra­to­ren. Das führt dazu, daß die Umschalt­pe­gel der Kom­pa­ra­to­ren vom Null­durch­gang der Ein­gangs­si­gna­le ver­scho­ben ist. Der Effekt ist nach­fol­gend etwas über­trie­ben dargestellt:

Als Fol­ge davon sind die erkann­ten posi­ti­ven Halb­wel­len kür­zer als die nega­ti­ven Halb­wel­len. Die­ser Feh­ler ist bei hohen Ein­gangs­pe­geln gering, wird aber immer höher, je wei­ter die Pegel sin­ken. Am Ende des Mess­in­ter­valls zei­gen die Zäh­ler­stän­de also die Dau­er der high- und low-Pha­sen an, also die Qua­li­tät des jewei­li­gen Signals.

Wei­te­re Funk­ti­ons­blöcke im FPGA

Inter­vall Timer T1

T1 ist ein ein­fa­cher Inter­vall Timer, der auto­ma­tisch nach einer ein­stell­ba­ren Zeit einen CPU-Inter­rupt gene­riert. Er wird im Bei­spiel­code auf 10 ms ein­ge­stellt. Anders als der in der CPU ein­ge­bau­te Timer, muß er nach dem Aus­lö­sen eines Inter­rupts nicht wie­der neu initia­li­siert wer­den. Damit wird also unab­hän­gig von der Inter­rupt-Ant­wort­zeit ein festes zeit­li­ches Raster erzeugt, zumin­dest solan­ge kei­ne Inter­rupts ver­lo­ren gehen.

Asyn­chro­ner Trans­mit­ter ATx

In der CPU kön­nen zwei ver­schie­de­ne UARTs ange­legt wer­den, ein „simp­le UART“ und ein „WB UART“. Kei­nes davon ist beson­ders gut doku­men­tiert, so daß es mir nicht auf Anhieb gelun­gen ist, einen Inter­rupt aus­zu­lö­sen, wenn der Sen­de­puf­fer leer ist. Da zum Sen­den der Mess­wer­te nur ein Trans­mit­ter nötig ist, schien es mir ein­fa­cher, einen eige­nen asyn­chro­nen Sen­der zu schrei­ben. Ich habe ihn ATx genannt, nicht UART, weil er weder uni­ver­sell ist noch einen Emp­fän­ger beinhal­tet. Immer­hin ist er gepuf­fert und löst einen Inter­rupt aus, sobald der Puf­fer ins Schie­be­re­gi­ster über­tra­gen wird. Die Baud­ra­te ist von der CPU ein­stell­bar. Es wird ein Stopp­bit gene­riert und Pari­ty ist nicht unterstützt.

Der RTL Code

Die oben im Block­dia­gramm gezeig­ten und anschlie­ßend erklär­ten Funk­tio­nen wur­den in Ver­i­log geschrie­ben und für das FPGA syn­the­ti­siert. Hier ist das gesam­te kom­pri­mier­te Gowin_V1.9.11.01 FPGA Desi­gner Pro­jekt inklu­si­ve der vom IP Core Gene­ra­tor erzeug­ten Modu­le. Mit wel­chen Para­me­tern die­se Modu­le erzeugt wur­den steht in src/readme.txt.

Das Top-Level Design fin­det sich in der Datei „Antennenkoppler.v“. Hier wer­den die FPGA-Pins defi­niert, die auf dem Pro­to­ty­pen­board ange­schlos­sen sind. Außer den gene­rier­ten Modu­len wer­den die Modu­le „Reflection_Measurement“, „ATx“ und „Inter­val­Ti­mer“ instan­zi­iert. Deren Quell­code fin­det sich in den ent­spre­chen­den Datei­en im sel­ben Verzeichnis.

Das Top-Level-Modul gene­riert die Reset-Signa­le für die CPU und die Refle­xi­ons­mes­sung, sowie die Signa­le für das Wish­bo­ne-Memo­ry-Inter­face. Es defi­niert auch die Signa­le für die Adress-Deco­die­rung. Wei­te­re Erläu­te­run­gen fin­den sich in den jewei­li­gen Quelltexten.

Außer­dem instan­zi­iert das Top-Level-Modul die PLL, die aus dem Ein­gangs­takt von 50 MHz den Abtast­takt von 200 MHz und den CPU-Takt von 20 MHz erzeugt.

Der IP Core Generator

Exem­pla­risch soll hier die Erzeu­gung des Gowin PicoRV32 CPU-Cores mit dem IP Core Gene­ra­tor gezeigt wer­den. Der IP Core Gene­ra­tor wird ent­we­der unter „Tools“-„IP Core Gene­ra­tor“ gestar­tet oder durch Anklicken des zuge­hö­ri­gen Icons mit den drei Wür­feln auf der Tools-Lei­ste. Er mel­det sich mit einem Ange­bot ver­schie­de­ner Hard- und Soft-Module:

Hier wählt man unter Soft IP-Core den Gowin_PicoRV32 mit einem Dop­pel­klick aus. Es wird eine Ein­ga­be­mas­ke zu Kon­fi­gu­ra­ti­on des gewünsch­ten Funk­ti­ons­um­fangs gestartet:

Durch Dop­pel­klick auf die jewei­li­ge Funk­ti­ons­grup­pe kann die­se aus­ge­wählt (grün hin­ter­legt) oder wie­der ent­fernt wer­den. Bei kon­fi­gu­rier­ba­ren Ele­men­ten öff­net sich ein ent­spre­chen­des Menü. Star­ten wir mit der Kon­fi­gu­ra­ti­on der CPU:

Gowin PicoRV32 CORE

In die­sem Dia­log wird der CPU-Core konfiguriert.

Man kann zunächst in engen Gren­zen den ver­wen­de­ten Befehls­satz aus­wäh­len. Immer aus­ge­wählt ist der RV32I Inte­ger Befehls­satz, optio­nal kann man die kom­pri­mier­ten Instruk­tio­nen (RV32C) und die Unter­stüt­zung für Inte­ger-Mul­ti­pli­ka­ti­on und Divi­si­on (RV32M) zu- oder abwäh­len. Dar­über­hin­aus sind eini­ge Fea­tures wähl­bar, die den Funk­ti­ons­um­fang und die Per­for­mance beein­flus­sen, was jeweils zugun­sten oder zuun­gun­sten der Anzahl ver­wen­de­ter Logik­gat­ter führt. Da es bei dem hier vor­ge­stell­ten System weder Ein­schrän­kun­gen bei den Logik­gat­tern noch bei der Per­for­mance gibt, las­sen wir alles auf den oben gezeig­ten Default-Einstellungen.

ITCM

Mit der etwas unge­wöhn­li­chen Abkür­zung ITCM wird der Befehls­spei­cher für den Pro­zes­sor kon­fi­gu­riert und sein Inhalt festgelegt.

Der Befehls­spei­cher kann 8 kB, 16 kB, 32 kB oder 64 kB groß sein. Für die­ses Pro­jekt belas­sen wir es bei dem Default-Wert von 32 kB.

Als näch­stes muß der Boot-Mode aus­ge­wählt wer­den. Bei den ersten bei­den Modi wird ein SPI-Inter­face gene­riert, über das das Pro­gramm beim Start aus dem exter­nen Flash in das ITCM gela­den wird. Damit ist die Soft­ware­ent­wick­lung qua­si unab­hän­gig von der Hard­ware­ent­wick­lung. Hier ist es im Moment aller­dings ein­fa­cher, die Soft­ware bereits in den FPGA-Bit­stream ein­zu­mas­sie­ren. Dazu wählt man „MCU boot and run in ITCM“ und trägt den Namen der zu ver­wen­den­ten Hex-Datei ein. Das hat aller­dings den Nach­teil, daß die Soft­ware bereits bei Gene­rie­rung des Bit­streams vor­han­den sein und die­ser bei jeder Soft­ware­än­de­rung neu gene­riert wer­den muß.

DTCM

Die Kon­fi­gu­ra­ti­on des Daten­spei­chers erfolgt ana­log zu der des Instruktionsspeichers.

Der Daten­spei­cher wird beim Star­ten durch die CPU initia­li­siert. Ein Hex-File wie beim Instruk­ti­ons­spei­cher ent­fällt daher.

WB UART

Das Wish­bo­ne UART wird tat­säch­lich in dem hier vor­ge­stell­ten Pro­jekt nicht mehr ver­wen­det, aber aus histo­ri­schen Grün­den sind die Signa­le noch defi­niert. Daher muß es enab­led wer­den, um Feh­ler­mel­dun­gen zu ver­mei­den. Es wird in einer zukünf­ti­gen Ver­si­on ent­we­der wie­der benutzt oder ganz entfernt.

WB SPI Master

Das SPI Master Inter­face wird zur Kom­mu­ni­ka­ti­on mit dem exter­nen AD-Wand­ler ADS1118 benö­tigt. Es wird enab­led und fol­gen­der­ma­ßen konfiguriert:

Das SPI-Inter­face wird mit dem CPU-Takt von 20 MHz betrie­ben und der AD-Wand­ler darf mit höch­stens 4 MHz getak­tet wer­den. Um genü­gend Mar­ge zu haben, wird er tat­säch­lich mit 2 MHz betrie­ben, was einem CLOCK_SEL=4 ent­spricht (SPICLK = CPUCLK/(2*(CLOCK_SEL+1))). Shift-Rich­tung, Clock Pha­se und Pola­ri­tät sind vom ADC vor­ge­ge­ben und müs­sen wie hier gezeigt gewählt wer­den, die Kom­mu­ni­ka­ti­on erfolgt mit einer Daten­län­ge von 32 Bit. Die ande­ren Para­me­ter blei­ben auf ihren Default-Werten.

Open WB Interface

Abschlie­ßend wird noch das Open Wish­bo­ne Inter­face enab­led. Es wird für das Lesen und Schrei­ben der Regi­ster in den oben genann­ten Funk­ti­ons­blöcken benötigt.

Ande­re CPU Funktionsblöcke

Simp­le UART, Open AHB Inter­face, WB I2C Master, WB SPI Slave, WB GPIO und ADV SPI-Flash wer­den hier nicht benutzt und dem­entspre­chend disabled.

Gene­rie­ren der kon­fi­gu­rier­ten CPU

Nach­dem nun alle Blöcke wie gewünscht kon­fi­gu­riert sind, kann nun die CPU gene­riert wer­den. Vor dem Klicken auf „ok“ soll­te aber noch der Haken bei „Use Gowin PicoRV32 as top modu­le“ abge­wählt wer­den, denn die CPU ist hier ein Sub-Modul.

Nach dem Klicken auf „ok“ wird noch nach­ge­fragt, ob das exi­stie­ren­de Ver­zeich­nis über­schrie­ben wer­den darf, falls es aus vor­he­ri­gen Durch­gän­gen bereits exi­stiert. Wir klicken auf „yes“ und nun hat er erst­mal 30 Sekun­den zu tun. Dann wird nach­ge­fragt, ob die neu gene­rier­ten Datei­en zum Pro­jekt hin­zu­ge­fügt wer­den sol­len. Das soll­te man beim ersten mal mit „yes“ beant­wor­ten, sonst muß man sie anschlie­ßend manu­ell hin­zu­fü­gen. Bei wei­te­ren Durch­gän­gen ist die Ant­wort egal, denn sie sind ja schon Teil des Projekts.

Syn­the­se, Pla­ce­ment & Routing

Jetzt ist das Pro­jekt bereit zur Syn­the­se. Dabei wer­den die Logik­funk­tio­nen auf­ge­löst und das gesam­te Pro­jekt in Logik­gat­ter umge­wan­delt. Wenn das feh­ler­frei durch­läuft, folgt anschlie­ßend das Pla­ce­ment & Rou­ting. Dabei wer­den die Pins und Gat­ter an die berech­ne­ten Posi­tio­nen plat­ziert und elek­trisch mit­ein­an­der verbunden.

Wenn die Syn­the­se für ein defi­nier­tes Pro­jekt ver­wen­det wer­den soll, dann muß man vor dem Pla­ce­ment zumin­dest die ange­schlos­se­nen Pins im Flo­or­plan­ner zuwei­sen. Nicht ange­schlos­se­ne Pins bekom­men eine Default-Kon­fi­gu­ra­ti­on (meist Input mit pull-up Wider­stän­den). Nicht im Flo­or­plan­ner zuge­wie­se­ne Signa­le wer­den irgend­wo­hin gelegt, wo es dem Pla­ce­ment genehm ist. Das kann man bei ersten Ver­su­chen machen, wenn es noch kei­ne Lei­ter­plat­te gibt. Das auto­ma­ti­sche Pla­ce­ment kann sogar eine Hil­fe bei der Pin­aus­wahl für das Lay­out sein.

Syn­the­se, Pla­ce­ment & Rou­ting benö­ti­gen für die­ses Pro­jekt auf mei­nem PC etwa 2 Minu­ten. Im Ver­lauf wer­den meh­re­re Berich­te erstellt, die man sich gele­gent­lich anschau­en soll­te. Sie kön­nen ins­be­son­de­re bei Feh­lern sehr hilf­reich sein. Auch die Timing Ana­ly­se soll­te man nicht igno­rie­ren. Hier wer­den die maxi­mal mög­li­chen Takt­fre­quen­zen ange­zeigt und zwar in rot, wenn die Vor­ga­ben nicht getrof­fen wer­den. Gera­de in der Ent­wick­lungs­pha­se kann man mal ein Auge zudrücken, wenn die Abwei­chun­gen gering sind und bei­spiels­wei­se nur 195 MHz statt 200 MHz erreicht wer­den, wir arbei­ten ja schließ­lich unter defi­nier­ten Bedin­gun­gen und bei Zim­mer­tem­pe­ra­tur. Sobald das Modul aber für den Außen­be­reich released wird und damit dem gesam­ten Tem­pe­ra­tur­be­reich und womög­lich Span­nungs­schwan­kun­gen aus­ge­lie­fert ist, wür­de ich kei­ne roten Mel­dun­gen mehr tole­rie­ren. Im ungün­stig­sten Moment fällt dann das Modul aus und man hat kei­nen Hin­weis, was schief­ge­lau­fen sein könnte.

Die Soft­ware

Hier zunächst das kom­plet­te Eclipse-Projekt:

Es müss­te direkt über­setz­bar sein, aber gege­be­nen­falls müs­sen Pfa­de ange­passt werden.

Das eigent­li­che Haupt­pro­gramm steht in der Datei „RiscV_Phasenmessung.c“. Im „libra­ry“ Ver­zeich­nis befin­den sich die Quell­codes, die von Gowin zur Ver­fü­gung gestellt wur­den, eini­ge von ihnen wur­den für die­ses Pro­jekt adap­tiert, ins­be­son­de­re die Inter­rupt-Funk­tio­nen in irq.c und irq.h.

Das Haupt­pro­gramm

Im Haupt­pro­gramm wer­den nach der Initia­li­sie­rung loka­ler Varia­blen als erstes die ver­wen­de­ten Peri­phe­rie­ele­men­te im FPGA initia­li­siert. Der asyn­chro­ne Sen­der wird auf 115,2 kBd ein­ge­stellt, der Inter­vall­timer auf 10 ms und ein Mess­zy­klus wird gestar­tet. Anschlie­ßend wer­den die Inter­rupts freigegeben.

Der Inter­vall­timer

Der Inter­vall­timer erzeugt alle 10 ms einen Inter­rupt, der die Funk­ti­on user1_int() auf­ruft. Dort wird im wesent­li­chen der näch­ste ADC Mess­zy­klus gestar­tet und das Ergeb­nis der vor­he­ri­gen Mes­sung gele­sen. Der SPI-Trans­fer zum ADC läuft auto­ma­tisch und die CPU muß nicht auf des­sen Ergeb­nis­se war­ten. Sie wer­den ein­fach bei der näch­sten Gele­gen­heit nach 10 ms abge­holt. Um die Aus­füh­rungs­zeit des Inter­rupt-Hand­lers kurz zu hal­ten, wer­den die Mess­ergeb­nis­se nicht kon­ver­tiert, son­dern als Roh­da­ten abgespeichert.

Die Pha­sen­mes­sung

Die Pha­sen­mes­sung erfolgt wie oben beschrie­ben im FPGA. Sie wird durch Schrei­ben des Con­trol­re­gi­sters gestar­tet und läuft dann auto­ma­tisch ab. Der Sta­tus der Mes­sung wird im Sta­tus­re­gi­ster ange­zeigt, das hier der Ein­fach­heit hal­ber gepollt wird. Ist die Mes­sung been­det, wer­den die Ergeb­nis­se aus­ge­le­sen, for­ma­tiert und die seri­el­le Über­tra­gung wird gestar­tet. Der Daten­trans­fer nach dem ersten Byte erfolgt inter­rupt­ge­steu­ert. Direkt nach dem Start der Über­tra­gung wird der näch­ste Mess­zy­klus gestartet.

Die For­ma­tie­rung der Ergeb­nis­se ist im Moment in les­ba­rer Form gehal­ten, damit man sie direkt in einem Ter­mi­nal­pro­gramm beob­ach­ten kann. Daher wur­de auch auf einen Zei­len­vor­schub ver­zich­tet, es wird also jedes­mal die vor­he­ri­ge Zei­le über­schrie­ben. Das wird in einem end­gül­ti­gen System sicher­lich adaptiert.

Bei­spiel­mes­sun­gen

Nach­fol­gend sol­len eini­ge Bei­spiel­mes­sun­gen z.Zt. noch mit rei­ner Text­aus­ga­be doku­men­tiert werden.

Mes­sung ohne Eingangssignale

Die­ser Block zeigt die gesam­te Text­aus­ga­be nach einem Reset. Die fol­gen­den Bei­spie­le zei­gen 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 Ver­sor­gungs­span­nung in Mil­li­volt, T die aktu­el­le Tem­pe­ra­tur des AD-Wand­lers. Es sind kei­ne Ein­gangs­si­gna­le ange­legt, daher ist die Fre­quenz 0 Hz. An den Aus­gän­gen der log­arith­mi­schen Ver­stär­ker lie­gen knapp 400 mV an.

21.104 MHz, F=0 dBm, R=0 dBm, ∆P=0°

Ff:  21.104 MHz, Fr:  21.104 MHz, Delta_P: +0.99°, High_f: 45.39%, High_r: 45.16%, ADC0=2 mV, ADC1=3303 mV, ADC2=1873 mV, ADC3=1864 mV, T=+25,844 °C

Hier wur­de absicht­lich eine krum­me Fre­quenz von 21.104 MHz gewählt, denn mit­un­ter erge­ben sich bei aus­ge­such­ten Ein­gangs­wer­ten zufäl­lig die gewünsch­ten kor­rek­ten Ergeb­nis­se. Die Fre­quen­zen wer­den kor­rekt gemes­sen und es zeigt sich eine Pha­sen­ver­schie­bung von +0.99°, was im Rah­men der Mess­ge­nau­ig­keit liegt. Die an den LogAmps gemes­se­nen Pegel lie­gen bei 1,87 V mit einer Abwei­chung von etwa 10 mV. High_f und High_r geben die rela­ti­ve High-Zeit des jewei­li­gen Signals wäh­rend der Mess­pe­ri­ode an. Sie lie­gen etwas unter 50%, wodurch die Mes­sung bei die­sem Ein­gangs­pe­gel schon unzu­ver­läs­sig wird.

21.104 MHz, F=20 dBm, R=0 dBm, ∆P=100°

Ff:  21.104 MHz, Fr:  21.104 MHz, Delta_P: +108.90°, High_f: 49.78%, High_r: 45.12%, ADC0=2 mV, ADC1=3303 mV, ADC2=2372 mV, ADC3=1862 mV, T=+26,813 °C 

Wir blei­ben bei der krum­men Fre­quenz und erhö­hen den Pegel des vor­lau­fen­den Signals auf +20 dBm und die Pha­sen­ver­schie­bung auf 100°. Die Fre­quenz­mes­sung bleibt kor­rekt und die Qua­li­tät der vor­lau­fen­den Signals ver­bes­sert sich auf 49.78%, also nahe­zu auf den Idealwert.

21.104 MHz, F=20 dBm, R=20 dBm, ∆P=100°

Ff: 21.104 MHz, Fr: 21.104 MHz, Delta_P: +101.99°, High_f: 49.77%, High_r: 49.65%, ADC0=2 mV, ADC1=3303 mV, ADC2=2371 mV, ADC3=2363 mV, T=+27,531 °C

Jetzt wird auch der Pegel des rück­lau­fen­den Signals auf +20 dBm erhöht. Die Qua­li­tät bei­der Signa­le nähert sich dem Ide­al­wert von 50% und die gemes­se­ne Pha­sen­ver­schie­bung liegt nun 2° neben dem tat­säch­li­chen Wert. Bei­de LogAmps zei­gen nun etwa 2,37 V Aus­gangs­span­nung an.

21.104 MHz, F=0 dBm, R=-15 dBm, ∆P=-100°

Ff:  21.104 MHz, Fr:  21.104 MHz, Delta_P: -103.13°, High_f: 45.24%, High_r: 29.50%, ADC0=0 mV, ADC1=3303 mV, ADC2=1871 mV, ADC3=1484 mV, T=+28,375 °C

Der Vor­lauf-Pegel liegt nun wie­der bei 0 dBm, der Rück­lauf-Pegel bei ‑15 dBm und die Pha­sen­ver­schie­bung wur­de am Signal­ge­nera­tor auf ‑100° ein­ge­stellt. Die Fre­quenz­mes­sun­gen sind nach wie vor kor­rekt, aber die Qua­li­tät des rück­lau­fen­den Signals sinkt auf unter 30%. Es wird also ein erheb­li­cher Teil der posi­ti­ven Halb­wel­le abge­schnit­ten. Trotz­dem weicht die gemes­se­ne Pha­sen­ver­schie­bung nur um gut 3° von der tat­säch­li­chen Ver­schie­bung ab.

21.104 MHz, F=0 dBm, R=-19 dBm, ∆P=-100°

Ff:  21.104 MHz, Fr:  21.104 MHz, Delta_P: -109.76°, High_f: 45.49%, High_r: 15.77%, ADC0=2 mV, ADC1=3303 mV, ADC2=1871 mV, ADC3=1385 mV, T=+27,500 °C

Der Rück­lauf-Pegel liegt jetzt bei ‑19 dBm. Die Fre­quenz­mes­sun­gen sind nach wie vor kor­rekt, aber die Qua­li­tät des rück­lau­fen­den Signals sinkt auf unter 16%.

21.104 MHz, F=0 dBm, R=-20 dBm, ∆P=-100°

Ff:  21.104 MHz, Fr:   0.000 MHz, Delta_P: -81.88°, High_f: 45.30%, High_r: 0.00%, ADC0=2 mV, ADC1=3303 mV, ADC2=1871 mV, ADC3=1358 mV, T=+27,500 °C

Der Rück­lauf-Pegel liegt jetzt bei ‑20 dBm, nur 1 dB weni­ger als bei der vori­gen Mes­sung. Das Signal wird nicht mehr erkannt, die Fre­quenz sinkt auf 0 Hz und die Qua­li­tät auf 0%. Kanal 3 des ADC zeigt noch 1,358 V, es liegt also noch ein hin­rei­chend star­kes Signal an.

Das ist genau das Pro­blem, das einer Nach­bes­se­rung bedarf und im näch­sten Rede­sign beho­ben wer­den soll.

Anmer­kun­gen zur PicoRV32 CPU

Im FPGA wird eine PicoRV32 CPU syn­the­ti­siert. Das ist ein 32-bit-Mikro­pro­zes­sor mit einer Risc-V-Archi­tek­tur. Zum Erstel­len des RTL-Codes wird der „IP Core Gene­ra­tor“ des Gowin FPGA Desi­gners benutzt. Alter­na­tiv kann der gesam­te Ver­i­log-Code auch von Git­Hub her­un­ter­ge­la­den wer­den. Damit hat man alle Frei­hei­ten, inklu­si­ve der Adap­ti­on des Codes, aber eben auch alle Last der kor­rek­ten Kon­fi­gu­ra­ti­on. Wei­te­re Infor­ma­tio­nen zur Risc-V-Archi­tek­tur fin­det man auf deren Web­site.

PicoRV32 Design­zie­le

PicoRV32 ist für die Syn­the­se für ein FPGA opti­miert. Dazu soll der Res­sour­cen­ver­brauch gering sein, die Rechen­lei­stung ist von unter­ge­ord­ne­ter Bedeu­tung. Im hier ver­wen­de­ten FPGA wird bei der Stan­dard­kon­fi­gu­ra­ti­on der CPU unge­fähr ein drit­tel der vor­han­de­nen Logik­gat­ter benötigt.

PicoRV32 Per­for­mance

Fälsch­li­cher­wei­se wird der Begriff RISC oft mit „eine Instruk­ti­on pro Takt“ gleich­setzt, was zwar oft stimmt, aber nicht die eigent­li­che Bedeu­tung ist. RISC bedeu­tet nur, daß der Befehls­satz auf sol­che Ope­ra­tio­nen redu­ziert ist, die ohne zeit­lich unbe­stimm­te Pipe­line-Locks aus­ge­führt wer­den kön­nen. Die Pipe­line darf durch­aus meh­re­re Tak­te für eine Instruk­ti­on benö­ti­gen, sie darf aber nicht bei­spiels­wei­se durch Wait-Sta­tes beim Zugriff auf ande­re Res­sour­cen auf unbe­stimm­te Zeit blockiert wer­den. Es sind nur sol­che Instruk­tio­nen erlaubt, die das sicher­stel­len. Damit fal­len ins­be­son­de­re die Befeh­le weg, die Daten im Spei­cher direkt modi­fi­zie­ren, wie es bei CISC-Pro­zes­so­ren üblich ist. Für Ope­ra­tio­nen die­ser Art wird in einem RISC Pro­zes­sor zunächst der Spei­cher­wert in ein Regi­ster gele­sen, dort modi­fi­ziert und anschlie­ßend zurück­ge­schrie­ben (Loa­d/­Sto­re-Archi­tek­tur). Wäh­rend des Lese- oder Schreib­zu­griffs kann die CPU prin­zi­pi­ell wei­ter­ar­bei­ten, was eine höhe­re Rechen­lei­stung ermög­licht. In einer ein­fach gehal­te­nen CPU darf man Opti­mie­run­gen die­ser Art aller­dings nicht erwar­ten. Auch die etwas kom­ple­xe­ren mathe­ma­ti­schen Ope­ra­tio­nen Mul­ti­pli­ka­ti­on und Divi­si­on benö­ti­gen eini­ge Dut­zend Taktzyklen.

Dhry­s­tone Bench­mark Ergebnisse

Die PicoR­V32-CPU benö­tigt beim Dhry­s­tone Bench­mark 4,1 Tak­te pro Instruk­ti­on, was 0,516 DMIPS/MHz ent­spricht. Das ist für RISC-Ver­hält­nis­se nicht über­wäl­ti­gend, aber in Anbe­tracht des gerin­gen Res­sour­cen­ver­brauch doch erstaun­lich gut. Auf jeden Fall ist es für die typi­schen embedded-Anwen­dun­gen mehr als aus­rei­chend. Eine Takt­fre­quenz von bis zu 50 MHz soll mög­lich sein, hier sind 20 MHz ein­ge­stellt und völ­lig ausreichend.

Inter­rupt Handling

Ein 32-bit RISC-Pro­zes­sor hat oft (aber nicht immer) 32 Regi­ster zur all­ge­mei­nen Ver­wen­dung. Das gilt auch für den PicoRV32. Das ist für vie­le Anwen­dungs­fäl­le groß­zü­gig und bequem, hat aber für Inter­rupt-Hand­ler den Nach­teil, daß beim Start des Hand­lers alle Regi­ster außer R0 gesi­chert und am Ende wie­der­her­ge­stellt wer­den müs­sen. Das dau­ert sei­ne Zeit. Für die hier ver­wen­de­te Vari­an­te RV32IMC wur­den typi­scher­wei­se 700 Tak­te zwi­schen dem Inter­rupt-Request und dem Inter­rupt-Ack­now­ledge gemes­sen. In die­ser Zeit ist der Inter­rupt-Hand­ler gestar­tet, hat die Regi­ster gesi­chert und springt zum Inter­rupt-Code des Anwen­ders. Des­sen Aus­füh­rungs­zeit, das Wie­der­her­stel­len der Regi­ster und der Rück­sprung in das unter­bro­che­ne Pro­gramm kommt zur gesam­ten Aus­füh­rungs­zeit noch dazu.

Es gibt eine RV32E-Ver­si­on, die die­ses Pro­blem ver­rin­gert, indem sie nur 16 Regi­ster unter­stützt. Sie wird aller­dings vom „IP Core Gene­ra­tor“ nicht angeboten.

Der Betrieb mit einem 10-MHz-Takt konn­te für den asyn­chro­nen seri­el­len Sen­der ATx inter­rupt­ge­steu­ert kei­nen kon­ti­nu­ier­li­chen Daten­strom bei 115 kBd errei­chen. Erst durch Takt­er­hö­hung auf 20 MHz wur­de das mög­lich. Das liegt natür­lich auch dar­an, daß in jedem Inter­rupt nur ein Byte in den Sen­der geschrie­ben wird. Mit einem Fifo aus meh­re­ren Bytes lie­ße sich das beheben.

Pro­ble­me

Einen schwer­wie­gen­den Feh­ler im Inter­rupt-Hand­ling konn­te ich loka­li­sie­ren, wenn­gleich noch nicht behe­ben. Zu Beginn der sprintf-Funk­ti­on und ihren Deri­va­ten wird die varia­bel lan­ge Para­me­ter­li­ste aus­ge­wer­tet. Das wird mit va_start() bewerk­stel­ligt und ist unge­wöhn­lich, weil bei fast allen ande­ren C‑Funktionen die Anzahl der Para­me­ter beim Auf­ruf bekannt ist. Wenn zwi­schen dem Funk­ti­ons­auf­ruf und va_start() ein Inter­rupt akzep­tiert wird (z.B. der Timer-Inter­rupt), dann pas­sie­ren schlim­me Din­ge. Als Work­around wer­den daher vor dem Auf­ruf alle Inter­rupts dis­ab­led und nach der Aus­füh­rung von va_start() wie­der enab­led. Das Pro­blem muß natür­lich zügig ana­ly­siert und beho­ben wer­den, aber für den Moment muß der Work­around reichen.

Nach­trag (07.02.2026)

Die Ursa­che des hier beschrie­be­nen Pro­blems ist gefun­den und beho­ben. Es ist ein Feh­ler in dem von Gowin ange­pass­ten Start­up code in der Datei start.S und betrifft den Inter­rupt Hand­ler. Vor dem Rück­sprung in das unter­bro­che­ne Pro­gramm wird der Inhalt des x31 (=t6) Regi­sters über­schrie­ben. Das ist ein tem­po­rä­res Regi­ster, des­sen Inhalt in vie­len Fäl­len nicht rele­vant ist, daher geht es meist gut. Es hat auch nichts mit der printf-Funk­ti­on zu tun, außer daß es da schnell auffällt.

Original-Code:
picorv32_getq_insn(x1, q1)
picorv32_getq_insn(x2, q2)

jal t6, return_from_irq

neuer korrekter Code:
picorv32_getq_insn(x1, q1)
picorv32_getq_insn(x2, q2)

picorv32_retirq_insn()

jal t6… springt unnö­ti­ger­wei­se in eine C‑Funktion und spei­chert die Rück­sprung­adres­se in t6. Ein Rück­sprung erfolgt aber nicht, denn es ist kein Funk­ti­ons­auf­ruf son­dern die Rück­kehr zum unter­bro­che­nen Pro­gramm. Das erle­digt die retirq Instruktion.

Sehr hilf­reich bei der Feh­ler­su­che war der Ori­gi­nal­code des PicoRV32 und das dazu­ge­hö­ri­ge readme.md-File.

Next Steps…

Ich wer­de in den näch­sten Wochen ein Rede­sign der Lei­ter­plat­te ange­hen. Dabei soll das Pro­blem der Digi­ta­li­sie­rung der Ein­gangs­si­gna­le beho­ben und die Lei­ter­plat­te deut­lich ver­klei­nert wer­den, denn die vie­len Test­mög­lich­kei­ten des Pro­to­ty­pen sind dann nicht mehr nötig.

Bis die Lei­ter­plat­te dann auf dem Tisch liegt, soll die PC-Soft­ware so ange­passt wer­den, daß der Daten­strom ana­ly­siert und das Mess­ergeb­nis im Smith-Dia­gramm dar­ge­stellt wird.