<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Christer's Ramblings</title>
    <description>Random stuff from the fingers of Christer Weinigel.
</description>
    <link>http://blog.weinigel.se/</link>
    <atom:link href="http://blog.weinigel.se/feed.xml" rel="self" type="application/rss+xml" />
    <pubDate>Mon, 26 Jul 2021 03:47:35 +0200</pubDate>
    <lastBuildDate>Mon, 26 Jul 2021 03:47:35 +0200</lastBuildDate>
    <generator>Jekyll v3.9.0</generator>
    
      <item>
        <title>Emacs and python.el</title>
        <description>&lt;p&gt;I use Emacs for most of my programming.  I do a lot of programming in
Python.  So quite naturally I use Emacs for a lot of Python programming.&lt;/p&gt;

&lt;p&gt;I started using python-mode.el a long time ago and have desperately
clung to it since then.  I just do not like how the newfangled
python.el does things at all.&lt;/p&gt;

&lt;p&gt;If I understand correctly, the idea with python.el is that you start a
long running Python process with C-c C-p (run-python) and then
incrementally feed it with snippets of code using C-c C-c
(python-shell-send-buffer) or C-c C-(r python-shell-send-region).&lt;/p&gt;

&lt;p&gt;I don’t like this.  This is not how I want to do things.  If I run the
following code multiple times with C-c C-c, x is going to increment:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;if 'x' not in locals():
    x = 0
x += 1
print(&quot;x&quot;, x)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;State stays between runs.  If I do:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;import module
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;and modify module.py, those changes won’t be picked up again unless I
kill the long running process and start it again.  Or I reload the
module by hand, but that quickly becomes unworkable if the module is
used in multiple places.  When I press C-c C-c to run my Python code I
want to start from a clean predictable slate.  I want a new fresh
instance of Python running in an empty buffer every time.  This way I
will know that there isn’t any state left over from previous runs.&lt;/p&gt;

&lt;p&gt;And, as a nice surprise, python.el silently strips out any code inside
a block like this:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;if __name__ == '__main__':
    main()
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;That made me quite confused the first time I tried to run a Python
program from within Emacs and nothing happened.  I ended up running it
from the command line instead.  When I got frustrated enough I started
digging into it and figured out what had happened.  I decided that
python.el was crap and went back to python-mode.el again.&lt;/p&gt;

&lt;p&gt;But now I have a machine with a new shiny Emacs 25 on it and my old
trust python-mode.el finally broke in such a way that I couldn’t fix
it.  I really don’t do lisp; I’ve never gotten into the language, so
my “lisp programming sessions” usually consist of reading what other
people on the internet have done and copying snippets into my .emacs.&lt;/p&gt;

&lt;p&gt;But this time I was frustrated enough so that I actually did something
about this and managed to hack together a function,
run-python-classic, which I have mapped to it C-c C-c so that Emacs
runs Python code from a buffer the way I want it to.&lt;/p&gt;

&lt;p&gt;As a small bonus I also added some code to parse any #! at the top of
the file to figure out which Python interpreter to run.  So if I have
a file with the following line at the top:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;#! /usr/bin/python3
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;C-c C-c will now use that information and start that version of
Python.  Very convenient when switching between maintaining old
Python 2 code and new Python 3 code.&lt;/p&gt;

&lt;p&gt;One small itch scratched.  Now I just have to figure out what I was
trying to actually write in Python before getting sidetracked.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;;; Parse any #! line at the top of the file to figure out what
;; interpreter to use
(defun get-script-interpreter ()
  (interactive)
  (let ((l (save-excursion
             (goto-char (point-min))
             (setq s (point))
             (move-end-of-line 1)
             (setq e (point))
             (buffer-substring-no-properties s e))))
    (if (string-match &quot;^\#![ \t]*\\(.*\\)&quot; l)
        (match-string 1 l))
    )
  )

;; Run Python in a classic way like old python-mode.el used to do.

(defun run-python-classic ()
  &quot;Run the python code in the current buffer in a new Python process.&quot;
  (interactive)

  ;; Kill any existing python process
  (ignore-errors (delete-process (python-shell-get-process)))

  ;; Erase an existing *Python* buffer or create a new one.
  ;; Make it visible and switch focus to it.
  (save-current-buffer
   (set-buffer (get-buffer-create &quot;*Python*&quot;))
   (let ((inhibit-read-only t)) (erase-buffer))
   (display-buffer (current-buffer))
   (switch-to-buffer-other-frame (current-buffer))
   )

  ;; If there is a #! line at the beginning of the buffer use it as
  ;; the command to start the interpreter.  If not, pass nil and use
  ;; the default interpreter
  (let ((cmd (get-script-interpreter)))
    (run-python cmd))

  ;; Since there shouldn't be any long lived data in the *Python*
  ;; buffer buffer, don't ask for confirmation when killing it.
  (set-process-query-on-exit-flag (python-shell-get-process) nil)

  ;; Finally send the buffer contents to the process and bypass the
  ;; code that strips out if __name__ == '__main__'.
  (save-restriction
    (widen)
    (let* ((process (python-shell-get-process))
           (string (buffer-substring-no-properties (point-min) (point-max))))
      (python-shell-send-string string process)
      )
    )
  )

;; This doesn't play well with the interpreter stuff, so disable it
(setq python-shell-completion-native-enable nil)

;; Finally add a hook to replace C-c C-c with the classic variant
(defun my-python-mode ()
  (setq py-indent-offset 4)
    (setq indent-tabs-mode nil)
      (define-key python-mode-map &quot;\C-c\C-c&quot; 'run-python-classic)
        )

(add-hook 'python-mode-hook 'my-python-mode)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
</description>
        <pubDate>Fri, 11 Sep 2020 00:00:00 +0200</pubDate>
        <link>http://blog.weinigel.se/2020/09/11/emacs-and-python-el.html</link>
        <guid isPermaLink="true">http://blog.weinigel.se/2020/09/11/emacs-and-python-el.html</guid>
        
        <category>nerdy stuff</category>
        
        <category>emacs</category>
        
        <category>python.el</category>
        
        <category>scratching an itch</category>
        
        
      </item>
    
      <item>
        <title>Strömmätning</title>
        <description>&lt;p&gt;Ja, tja, jag har till och från jobbat med små batteridrivna prylar.
En fråga jag alltid fått från chefen är “hur ofta behöver man byta
batterier?”  Det är rätt viktigt att veta en sån sak.  Om en pryl kan
klara sig på ett knappcellsbatteri i ett par år så är det jättebra, om
man måste ha 4 AA-batterier som behöver bytas varannan månad så
sabbar det lätt kundens upplevelse av produkten.  Och det är en
jättesvår fråga att svara på.&lt;/p&gt;

&lt;p&gt;För en ficklampa är det inte så svårt, den drar ingenting när den är
avstängd och om lysdioderna sen behöver 100mA när lampan är tänd så
kan man rätt lätt räkna ut att man får ungefär 8 timmars batteritid
med ett batteri på 800mAh.  Eller så har man i varje fall en
någorlunda enkel och jämn urladdningskurva som man kan räkna på.&lt;/p&gt;

&lt;p&gt;En IoT-pryl brukar ha ett mycket mer dynamiskt beteende.  Prylen är
för det mesta i sleep mode och drar några microampere.  Sen vaknar den
till liv och då drar CPUn ett par milliampere.  Sen drar igång radion
för mottagning och då drar den ytterligare 10mA under 50
milllisekunder medans den sitter där och lyssnar.  Om den behöver
svara så måste den även sända och då går strömförbrukningen upp till
20mA under 3 millisekunder.  Har prylen en lysdiod som blinkar till så
kanske det drar 1mA under 100ms.  Osv.&lt;/p&gt;

&lt;p&gt;En pryl som uCurrent är nästan oanvändbar för sånt här, om man ställer
in skalan på den så man kan mäta strömförbrukningen i sleep mode så
kommer spänningsfallet över shuntresistorn (burden voltage) vara så
högt vid 10mA att prylen dör på grund av för låg matningsspänning.
Man kan försöka komma runt det här genom att sätta dioder över
shuntresistorn.  Då begränsar man spänningsfallet till ett par hundra
millivolt, men om man t.ex. har en pryl som strömförsörjs direkt från
ett 3V knappcellsbatteri så är ett par hundra millivolt rätt mycket.
En fin dyr bänkmultimeter som kan mäta ner till microampere har ofta
samma problem.  Sen är en multimeter rätt dålig på att visa dynamiska
förlopp, man kan bara mäta på strömmar som inte varierar för mycket.&lt;/p&gt;

&lt;p&gt;Man kan komma med en rätt bra gissning ändå, om prylen har en testmode
där man t.ex. kan säga åt den att slå på radion i mottagningsläge och
sen hänga där.  Då kan man mäta strömförbrukningen med en noggrann
multimeter och sen titta på hur länge prylen är i respektive läge med
ett oscilloskop.  Sen är det bara att ta mätningarna för varje läge
och räkna ut amperetimmar=ampere*sekunder/3600 och summera ihop dem.
Det är jobbigt och tråkigt men det fungerar någorlunda bra.  Fast med
moderna IoT-prylar med bluetooth low energy som växlar mellan flera
olika strömsparlägen och där man inte kommer åt testmoderna så blir
det jobbigare och jobbigare att göra sånt här för hand.&lt;/p&gt;

&lt;p&gt;I rätt många år har jag haft i bakhuvudet att det borde gå att göra
något bättre.  Hur svårt ska det vara att bygga något som både klarar
av mäta höga strömmar på säg 100mA men som också kan mäta små strömmar
ner till några mikroampere?  Och som kan göra det utan för stort
spänningsfall över mätaren?  Och som kan logga strömförbrukningen med
någon millisekunds upplösning så att man kan se strömförbrukningen
över tid?&lt;/p&gt;

&lt;p&gt;En modern multimeter som en Keithley DMM6500 för 10000 kronor kan
logga mätningar över tid så den kanske skulle kunna göra allt det där,
men det är rätt mycket pengar för ett mätinstrument.  Jag vet inte
heller om den klarar av att växla mellan mätområden snabbt nog.&lt;/p&gt;

&lt;p&gt;Jag har i varje fall skissat och funderat på en sån här mätpryl till
och från i flera år.  Jag har läst på om hur en transimpedance
amplifier (TIA) fungerar: de är väldigt bra på att mäta väldigt låga
strömmar utan att burden voltage blir för stor.  Jag har funderat på
hur man kan utöka området som en sån förstärkare klarar av att driva
hålla nere burden voltage, och på sätt att hålla nere burden voltage
när förstärkaren ändå inte orkar längre.  Jag har simulerat och byggt
en massa testkretsar.  Men sen har det kommit jobb eller andra saker
emellan och så har projektet hamnat på hyllan igen.&lt;/p&gt;

&lt;p&gt;För ett par veckor sen bestämde jag mig för att faktiskt få ändan ur
och göra det här på riktigt.  Jag tog de kretsar som fungerat bäst i
simulering och ritade ett kretskort där jag stoppade in alltihop.  Jag
fick mönsterkorten förra fredagen och har suttit i verkstan och lött
och mätt och provat saker på kvällarna efter jobbet.&lt;/p&gt;

&lt;p&gt;Det jag byggt är en kombination av en vanlig ampere-meter med en
10mOhm shunt-resistor.  Den klarar av att mäta ström upp till +/-3A
och ner till +/-100µA innan signalen försvinner i bruset.  Sen har jag
en transimpedance amplifier i serie med den som kan mäta upp till
+/-300µA och ner till +/-10nA eller så innan bruset tar över.
Transimpedance amplifiern i sig har jag modifierat så att den klarar
av att kompensera för spänningsfallet över mätkretsen ända upp till
+/-200mA.  För att burden voltage inte ska bli för stor vid strömmar
över det så har jag en MOSFET som kortsluter ingången till
transimpedance amplifiern när strömmen genom shunt-resistorn är över
+/-100mA.  Överlappet mellan mätområdena borde göra att jag kan mäta
ström hela vägen från +/-10nA upp till +/-3A och MOSFETen borde göra
så att kretsen beter sig som om den bara hade 10mOhm shunt-resistans
(plus lite till för MOSFETen) hela vägen upp till 3A.&lt;/p&gt;

&lt;p&gt;Och tamigtusan om det inte verkar fungera.  Eller ja, det verkar
fungera efter att ha fulpatchat en del misstag i min konstruktion, men
sånt får man väl räkna med.&lt;/p&gt;

&lt;p&gt;Om man börjar nerifrån.  Så här ser det ut om jag sveper min
strömkälla från -10nA till +10nA i steg om 1nA.  Inte bra, man kan ju
se en trend, men i början så var ju mätvärdena helt fel.  Om jag
skulle göra samma mätning en gång till så lär jag få en helt annan
kurva för att det är så mycket brus och mätvärdena driver fram och
tillbaka.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2019-08-31-strommatning/m1.png&quot; alt=&quot;png&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Svep från -100nA till +100nA i steg om 10nA.  Mycket bättre.  Det
finns ett litet gain-fel, om man tittar på Y-axeln så rapporterar
kretsen ett lite högre värde än 100nA för en ström in på 100nA.
Gainen verkar vara några procent högre än vad den borde vara.  (Eller
så är det min strömkälla som levererar ett par procent högre ström än
vad den borde.)  Men utöver det så ser det ju jättebra ut.&lt;/p&gt;

&lt;p&gt;I verkligheten så har utsignalen en DC-offset på ungefär 13uV som jag
räknat bort innan jag översatt utspänningen i volt till ampere.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2019-08-31-strommatning/m2.png&quot; alt=&quot;png&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Svep från -1µA till +1µA i steg om 100nA.  Rakt och fint och inget
märkbart gain-fel.  Den röda linjen är alltså från
transimpedance-amplifiern som mäter små strömmar.  Den gröna linjen är
mätvärdet från shunt-resistorn, men det är bara bruset som syns vid så
låga strömmar.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2019-08-31-strommatning/m3.png&quot; alt=&quot;png&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Svep från -10µA till +10µA i steg om 1µA. Inga konstigheter. Den röda
linjen från transimpedance amplifiern ser bra ut. Den gröna linjen
från shunt-resistorn börjar gå åt rätt håll, men den är fortfarande
skräp.&lt;/p&gt;

&lt;p&gt;Fonten som används i skalan är trasig, fyrkanten borde vara “µ”.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2019-08-31-strommatning/m4.png&quot; alt=&quot;png&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Svep från -100µA till +100µA. Nu kommer man till området där
mätningarna från shunt-resistorn börjar bli användbara. Mätvärdena är
fortfarande brusiga och det finns en offset men det är nästan
användbart. Mätningarna från transimpedance amplifiern ser fortfarande
bra ut.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2019-08-31-strommatning/m5.png&quot; alt=&quot;png&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Svep från -1mA till +1mA. Här tog transimpedance-amplifierns mätområde
slut. Den förstärker med 10000V/A och utspänningen kan variera mellan
ungefär +/-3.5V vilket gör att den bara är linjär upp till ungefär
+/-300µA. Men här börjar ju mätningnarna från shunt-resistorn vara bra
nog att ta över.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2019-08-31-strommatning/m6.png&quot; alt=&quot;png&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Svep från -10mA till +10mA. Inga konstigheter här heller. Mätvärdet
från transimpedance amplifiern är oanvändbart vid de här strömmarna
även om den fortfarande ser till att burden voltage är låg. Mätvärdet
från shunt-resistorn är snyggt och linjärt dock.&lt;/p&gt;

&lt;p&gt;Tyvärr så lyckades jag inte få med burden voltage i mätningarna jag
gjorde innan jag smet från verkstan igår, men om jag minns rätt så
ligger den fortfarande på enstaka millivolt här.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2019-08-31-strommatning/m7.png&quot; alt=&quot;png&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Svep från -100mA till +100mA. Nånstans här så har MOSFETen slagt till
och kortslutit transimpedance amplifiern.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2019-08-31-strommatning/m8.png&quot; alt=&quot;png&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Svep från -1A till +1A. Inga konstigheter.&lt;/p&gt;

&lt;p&gt;Det här är så långt jag kunde köra med min fin-strömkälla. Men jag har
även testat för hand med ett labbaggregat att kretsen klarar av att
leverera ett vettigt mätvärde upp till +/-3A och att burden voltage
ligger kring +/-30mV då. Vid +/-5A så får man inte ut några vettiga
mätvärden längre, men burden voltage höll sig kring +/-50mV.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2019-08-31-strommatning/m9.png&quot; alt=&quot;png&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Så, tja, själva biten som tar en ström in och levererar en spänning ut
på två områden verkar ju fungera. Nu är det bara resten kvar.&lt;/p&gt;

&lt;p&gt;Jag har köpt en flerkanals 24-bitars 100ksps A/D-omvandlare som jag
tänkt använda för att få in spänningarna i en microcontroller. Men
först behövs det lite signalnivåanpassning och lågpassfilter mellan
mätkretsen och A/D-omvandlaren. Sen måste jag skriva nån slags
firmware för microncontroller också som kan strömma samples över USB
till en PC eller spara ner det på ett SD-kort.&lt;/p&gt;

&lt;p&gt;Jag återkommer väl och berättar hur det gick om ytterligare ett par år.&lt;/p&gt;
</description>
        <pubDate>Sat, 31 Aug 2019 00:00:00 +0200</pubDate>
        <link>http://blog.weinigel.se/2019/08/31/strommatning.html</link>
        <guid isPermaLink="true">http://blog.weinigel.se/2019/08/31/strommatning.html</guid>
        
        <category>nerdy stuff</category>
        
        <category>current measurements</category>
        
        
      </item>
    
      <item>
        <title>Simulating Kicad schematics with Spice</title>
        <description>&lt;p&gt;I’ve been using Tina TI to do simulation of electronic circuits for a
long time.  Tina can be kind of frustrating though since it’s a GUI
application so for almost as long I’ve had this idea about finding
some other simulator that can be scripted in some way to run a batch
of simulations where I can do variants of each simulation, maybe with
different component values and with different stimuli.  But most other
simulators I’ve looked at such as qucs are just as “bad” in that they
are hard to script.&lt;/p&gt;

&lt;p&gt;Most of the things I want to be able to do can be done with one of the
free spice simulators such as Ngspice, but the raw Spice language is
kind of painful to use.  It would be nice if I could just draw a
schematic in Kicad and then export a netlist which can be used with
Ngspice, but I have not been able to get that to work the way I want it
to.  The netlists lack some information that is present in the
schematics that would be good to have in a simulation.&lt;/p&gt;

&lt;p&gt;A few weeks ago I got so frustrated that I started writing my own
parser which can read a Kicad schematic file and extract the
components and connectivity from those.  And for some strange reason I
also wrote a renderer which can render the schematic using matplotlib
as a backend.  This turned out to have it’s advantages…&lt;/p&gt;

&lt;p&gt;Today I finally put together the missing parts, a converter which
translates the Kicad schematic (including any spice model directives)
to a Ngspice compatible circuit, runs ngspice in batch mode and then
retrieves all the resulting values from the rawfile that Ngspice
outputs.&lt;/p&gt;

&lt;p&gt;And it actually seems to work decently.&lt;/p&gt;

&lt;p&gt;So here’s what I can do right now.&lt;/p&gt;

&lt;p&gt;First I have to import the usual modules to use matplotlib and numpy:&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;matplotlib.pyplot&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;plt&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;numpy&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;np&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;I can then load a kicad schematic:&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kn&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;kicad.sch&lt;/span&gt; &lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Sch&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;sch&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Sch&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'../simulation/tia/tia.sch'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;and render the kicad schematic in a matplotlib plot.  This is a normal
simple transimpedance amplifier using the spice model for a LMH6624
OP-amp:&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kn&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;kicad.render_matplotlib&lt;/span&gt; &lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;render_matplotlib&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;fig&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ax&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;plt&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;subplots&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;figsize&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;15&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;fig&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;subplots_adjust&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;left&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;0.1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;right&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;0.9&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;top&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;0.9&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;bottom&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;0.1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;render_matplotlib&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ax&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;sch&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;ax&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;axis&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'equal'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;plt&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;show&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;img src=&quot;/images/2018-08-14-kicad-spice-simulation/test-ngspice_5_0.png&quot; alt=&quot;png&quot; /&gt;&lt;/p&gt;

&lt;p&gt;I can then convert the Kicad schematic to a spice circuit:&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kn&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;kicad.spice_converter&lt;/span&gt; &lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;sch_to_spice&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;circuit&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;sch_to_spice&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;sch&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;All I have to do then is start Ngspice:&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kn&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;spice.ngspice&lt;/span&gt; &lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;NgSpice&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;spice.unit&lt;/span&gt; &lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;spice&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;NgSpice&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;and run something simple such as a DC sweep and plot it:&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;data&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;spice&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;dc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'hello world'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;circuit&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;'i101'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;m&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;m&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;u&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;plt&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;plot&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'v(i-sweep)'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;values&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'v(vout)'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;values&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;plt&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;show&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;img src=&quot;/images/2018-08-14-kicad-spice-simulation/test-ngspice_9_0.png&quot; alt=&quot;png&quot; /&gt;&lt;/p&gt;

&lt;p&gt;And here comes the fun part.  I can now run multiple simulations, and
then show them in one plot.  For example this is an AC simulation
where I plot the frequency response for multiple different values of
capacitor C101:&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;values&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;p&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;10&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;p&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;100&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;p&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;n&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;10&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;n&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;v&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;values&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;circuit&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;devices&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'C101'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;value&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;v&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;data&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;spice&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ac&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'hello world'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;circuit&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;'dec'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;G&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;plt&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;plot&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'frequency'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;values&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;real&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
             &lt;span class=&quot;mi&quot;&gt;20&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;np&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;log10&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;np&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;abs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'v(vout)'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;values&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)),&lt;/span&gt;
             &lt;span class=&quot;n&quot;&gt;label&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;str&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;v&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;plt&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;xscale&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'log'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;plt&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;legend&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;plt&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;show&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;img src=&quot;/images/2018-08-14-kicad-spice-simulation/test-ngspice_11_0.png&quot; alt=&quot;png&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Even nicer, since matplotlib has lots of backends this works just
about anywhere, including the rendering of the schematics.  I can run
the above commands with Python on Linux and show the schematics
and plots in a normal X window.  And since matplotlib can produce
beautiful plots in a bunch of different formats (PNG, SVG, PDF) I can
generate nice plots in all of them.&lt;/p&gt;

&lt;p&gt;Originally I played around with wxPython and displayed my matplotlib
plots in an embedded frame.  My idea was to write a GUI (yeah, yet
another GUI) that would let me set up and arrange multiple simulations
and plots in a Window.  But then I realised that since matplotlib is
supported in &lt;a href=&quot;http://jupyter.org/&quot;&gt;Jupyter&lt;/a&gt; and that the easiest way
to play around with and test things is to just use an interactive
notebook.  A big part of this blog post was actually generated by
Jupyter and translated to Markdown with jupyter-nbconvert.&lt;/p&gt;

&lt;p&gt;And it really is simple and convenient.  If I make a change in the
Kicad schematics, I can just reload it and rerun &lt;em&gt;all&lt;/em&gt; my simulations
with one command.  If I just want try out some component values I
don’t even have to leave the notebook since I can easily change them
with some code.&lt;/p&gt;

&lt;p&gt;Of course, this is just a quick hack so far and the code to do all
this is ugly as sin.  It’s nowhere near complete either.  I’ve only
added support for the R, C, L, I and X devices so far, enough to
verify that my first simple schematic can be simulated.  Only AC and
DC simulations work so far, but it should be trivial to add transient
and noise simulations.&lt;/p&gt;

&lt;p&gt;Even as incomplete as this all is, I must say that it feels really
promising.&lt;/p&gt;
</description>
        <pubDate>Tue, 14 Aug 2018 00:00:00 +0200</pubDate>
        <link>http://blog.weinigel.se/2018/08/14/kicad-spice-simulation.html</link>
        <guid isPermaLink="true">http://blog.weinigel.se/2018/08/14/kicad-spice-simulation.html</guid>
        
        <category>nerdy stuff</category>
        
        <category>kicad</category>
        
        <category>spice</category>
        
        <category>ngspice</category>
        
        <category>matplotlib</category>
        
        <category>jupyter</category>
        
        
      </item>
    
      <item>
        <title>Capturing ADC data into DDR memory on the SDS7102</title>
        <description>&lt;p&gt;This is a post in a series about me poking at the insides of my OWON
SDS7012 oscilloscope.  You might want to start reading at the
&lt;a href=&quot;/2016/05/01/sds7102-hacking.html&quot;&gt;beginning&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I’m finally on vacation and have had time to do a bit more work on my
custom FPGA image for the scope.&lt;/p&gt;

&lt;p&gt;First of all, I’ve managed to make the emulated DDR2 memory on the SoC
bus read/write.  This means that I can use the fast SoC bus for both
controlling the FPGA and to read data out of the FPGA.  This is good
because it means that everything is fast.  The bad part is that I’ve
had to hardcode some IODELAYs in the FPGA image.  The IODELAYs differ
between different production lots of FPGAs so the delays that work on
my scope most probably won’t work on someone else’s scope.  I ought to
figure out how to fix this properly, either by calibrating the delays
or by using the DQS strobes instead of delays, but well, I don’t think
anyone else has dared to try out my firmware on their SDS7102 scope
yet, so it probably doesn’t matter that much.&lt;/p&gt;

&lt;p&gt;The most visible thing I’ve done though is that I’ve managed to
capture samples from the ADC into the DDR2 memory connected to the
FPGA.  This means that I can capture up to 64 million samples of data
at 1 gigasamples/second.  Both fast and deep memory and the same time.&lt;/p&gt;

&lt;p&gt;As usual it wasn’t smooth sailing to get here.  I first tried
capturing data to DDR memory using just the 64 word FIFO in the memory
controller but a 64 word FIFO wasn’t quite enough, sometimes the FIFO
would become full and I’d drop a few samples.  Not good.&lt;/p&gt;

&lt;p&gt;When this didn’t work I tried adding a FIFO in between the ADC and the
DDR2 memory.  This worked better but still dropped samples every now
and then.  I finally realised that this is because the write full flag
(wr_full) of the Xilinx memory controller is registered so I need to
wait one clock cycle before it reflects a FIFO full condition.  But to
keep up with the data from the ADC I really need to write to the FIFO
on every clock cycle, so this won’t work.  It took some time to
realise what was happening, but when I did the fix was fairly easy,
look at the FIFO count register (wr_count) and stop writing to the
FIFO a few words before it fills up.&lt;/p&gt;

&lt;p&gt;I’m also having problems with closing timing, some parts of my design
is just too slow according to the Xilinx synthesis tools.&lt;/p&gt;

&lt;p&gt;But even with the timing violations it seems to work in real life.&lt;/p&gt;

&lt;h1 id=&quot;pretty-graphs&quot;&gt;Pretty Graphs&lt;/h1&gt;

&lt;p&gt;I can now make a capture from the ADC into DDR2 memory on the scope
and then extract 1 millisecond worth of data
(400 000 samples at 400 megasamples per second and channel),
transfer it to a Linux PC, use python to post-process the data and then
make pretty graphs like this:&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/images/2016-08-15-sds7102-ddr-capture/samples-400000.png&quot;&gt;&lt;img src=&quot;/images/2016-08-15-sds7102-ddr-capture/samples-400000.png&quot; alt=&quot;1ms of data (400000 samples)&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is with both channels connected to the probe compensation output
on the scope.  Channel 1 (the red channel) is set for high gain and
the signal disappears off the screen at the top.  Channel 2 (the green
channel) has a lower gain and fits on the screen.  I’ve cheated a bit
and have offset the channels by 50µs in post-processing to make them
easier to tell apart.&lt;/p&gt;

&lt;p&gt;As you can see I’ve tried to make the signals look a bit “analog”.  On
an analog scope a steady signal will stay in place and illuminate the
phosphor for a longer time and make the phosphor in that location
brighter.  A faster changing signal will illuminate the phosphor in
each spot for a shorter time and the phosphor won’t be as bright.&lt;/p&gt;

&lt;p&gt;Each pixel in this graph represents 1000 samples which are weighted
together to decide the intensity of each pixel.  If you look at the
rising edge of the green signal it is brighter at the top where it’s
changing more slowly.  The falling green edge and the red edges are
changing a lot faster and are also less bright.&lt;/p&gt;

&lt;p&gt;The size of the graph is 400x256 pixels.  My thought is that if I
scale this image to 800x512 pixels it should fit perfectly on the
800x600 screen of the oscilloscope, leaving 88 pixels at the bottom
and the top of the screen for the user interface.  The weighting
algorithm doesn’t try to be accurate and match the behavior of
phosphor in any real sense, I’m only trying to make it “look good” and
provide some useful information.&lt;/p&gt;

&lt;p&gt;I can take the same captured waveform and zoom in a bit and show 1µs
(4000 samples) worth of data:&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/images/2016-08-15-sds7102-ddr-capture/samples-4000.png&quot;&gt;&lt;img src=&quot;/images/2016-08-15-sds7102-ddr-capture/samples-4000.png&quot; alt=&quot;1µs of data (4000 samples)&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Each pixel represents only 10 samples and the weighting algorithm
can’t do as much with the intensity anymore.  It’s possible to see
that there’s some kind of ringing or noise at the bottom of each
falling edge though.&lt;/p&gt;

&lt;p&gt;This is a graph at maximum zoom with 100ns (400 samples) worth of
data:&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/images/2016-08-15-sds7102-ddr-capture/samples-400.png&quot;&gt;&lt;img src=&quot;/images/2016-08-15-sds7102-ddr-capture/samples-400.png&quot; alt=&quot;100ns of data (400 samples)&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;There really isn’t any difference in intensity anymore since each
pixel represents 1 sample and the weighting algorithm can’t do that
much with it.  The graph isn’t as pretty anymore, but I’d say it still
looks OK.  And the ringing from the previous graph is clearly visible.&lt;/p&gt;

&lt;h1 id=&quot;but&quot;&gt;But…&lt;/h1&gt;

&lt;p&gt;There’s one big problem with these pretty graphs.  They take a lot of
post-processing to make.  The more data that has to be weighted the
slower it is.  The first graph with 1000 samples per pixel take about
10 seconds to create on a PC.  Of course, this is using floating point
in Python which isn’t the fastest language when it comes to processing
individual pixels, so it can be optimized.  But even then, even
rewritten in C i doubt that the ARM CPU in the scope can produce a
frame rate of 30Hz if it has to process many thousands of samples per
frame.&lt;/p&gt;

&lt;p&gt;I do have an idea that it might be possible to do in the FPGA.  If I’m
lucky that is, it’s quite possible that what I want to do won’t fit in
the available logic elements of the scope and then I’ll have to settle
for something that isn’t quite as pretty as these graphs.&lt;/p&gt;

&lt;p&gt;Yes, another thing.  The reason I’m sampling at 400 megasamples/second
instead of at 500 megasamples/second (which is the maximum rated
sample rate for two channels or a total of 1 gigasamples/second) is
that if I increase the sampling rate I start seeing a lot of noise:&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/images/2016-08-15-sds7102-ddr-capture/samples-500000.png&quot;&gt;&lt;img src=&quot;/images/2016-08-15-sds7102-ddr-capture/samples-500000.png&quot; alt=&quot;100ns of data at 500 megasamples/second (500 000
samples)&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It’s not an awful lot of noise, but it’s a lot worse than the clean
graphs above at 400 megasamples/second.  I’m not sure if this is a
digital problem with the ADC outputs (which might be solvable with a
few strategical IODELAYs in the FPGA) or if noise, jitter, resonance
or something else from some other part of the scope couples onto the
actual analog signals before the ADC.  For the moment I’ll just run
the scope at 400 megasamples/second and be happy with that, but it
would be nice to figure out where this noise comes from.&lt;/p&gt;

&lt;p&gt;Well, I’ll sit and experiment with visualization algorithms in the
FPGA for a while and see what I can do.&lt;/p&gt;
</description>
        <pubDate>Mon, 15 Aug 2016 00:00:00 +0200</pubDate>
        <link>http://blog.weinigel.se/2016/08/15/sds7102-ddr-capture.html</link>
        <guid isPermaLink="true">http://blog.weinigel.se/2016/08/15/sds7102-ddr-capture.html</guid>
        
        <category>nerdy stuff</category>
        
        <category>sds7102</category>
        
        <category>fpga</category>
        
        
      </item>
    
      <item>
        <title>The fast buses on the SDS7102</title>
        <description>&lt;p&gt;This is a post in a series about me poking at the insides of my OWON
SDS7012 oscilloscope.  You might want to start reading at the
&lt;a href=&quot;/2016/05/01/sds7102-hacking.html&quot;&gt;beginning&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;As you might recall there is one very important bus in the scope that
connects the &lt;a href=&quot;/2016/07/10/sds7102-soc-bus.html&quot;&gt;SoC to the
FPGA&lt;/a&gt;.  The FPGA is
supposed to emulate a DDR2 memory connected in parallel with the real
DDR2 memory to the SoC.  This is a fairly fast bus running at 133MHz
with data being transferred on both edges of the clock.&lt;/p&gt;

&lt;h1 id=&quot;how-hard-can-it-be-anyway&quot;&gt;“How hard can it be anyway?”&lt;/h1&gt;

&lt;p&gt;The thing that makes a DDR bus a bit tricky is is that it actually
works in multiple different clock domains at the same time.  For a 16
bit DDR bus there are three clock domains, one for the control
signals, and one for each of the 2 bytes of data.  For a 32 bit DDR
bus there are five clock domains, control + 4 bytes of data.&lt;/p&gt;

&lt;p&gt;The DDR controller control signals are always driven by the SoC and
are synchronous to the DDR clock.  These are the chip select (/CS),
row address select (/RAS), column address select (/CAS), write enable
(/WE), bank address (BA[1:0]) and address (BA[11:0]) signals.  These
signals are single data rate (SDR) which means that they should be
sampled on the positive edge of the DDR clock.&lt;/p&gt;

&lt;p&gt;Data is transferred on the DQ signals using the DQS signals as a
clock.  Data is transferred at double data rate (DDR), that is, on
both the positive and negative edge of the DQ signal.  Each byte worth
of DQ signals has its own DQS signal.  The DQS clock should be running
at the same frequency as the DDR clock but can vary slightly and can
also be slightly off in timing for each byte.&lt;/p&gt;

&lt;p&gt;The reason each byte worth of DQ signal has its own DQS signal is that
many DDR DIMMs are built using DDR chips that only have an 8 bit wide
bus.  Each chip has its own timing and own DQS signal.&lt;/p&gt;

&lt;p&gt;For writes there are is a /DM signal present for each byte.  A low on
/DM indicates that the byte contains data that should be written to
memory, a high on /DM indicates that no write should be performed.
The timing for the /DM signal is the same as for the DQ signal.&lt;/p&gt;

&lt;p&gt;Someone who wants to implement a 16 bit DDR memory properly has to do
something like this:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2016-07-25-sds7102-fast-buses/ddr.png&quot; alt=&quot;DDR memory&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Each byte worth of data needs its own FIFO which is clocked by the DQS
signal.  LDQS is the lower data strobe for the low byte, UDQS is the
upper data strobe for the upper byte.  Not that easy to get right.&lt;/p&gt;

&lt;h1 id=&quot;read-only-ddr-memory&quot;&gt;Read only DDR memory&lt;/h1&gt;

&lt;p&gt;Then I realised that the bulk of all data transfers are going to be
sample data from the FPGA to the SoC.  For the few transfers I have to
do from the SoC to the FPGA I can use the slow SPI bus I already have
and have been using for a while.  So I can get away with emulating a
read only DDR memory, and in that case I can transfer all data using
the DDR clock and don’t have to care about multiple clock domains.&lt;/p&gt;

&lt;p&gt;That would make everything much easier, all I have to do then is to
figure out how to use the ODDR2 primitives of the Xilinx FPGA to be
able to transfer data using DDR on both edges of the clock.&lt;/p&gt;

&lt;p&gt;I could even use a read from a specific address to tell the FPGA that
I have finished reading data and that it can perform a new capture.&lt;/p&gt;

&lt;p&gt;I could probably manage that.&lt;/p&gt;

&lt;h1 id=&quot;ddr-protocol&quot;&gt;DDR Protocol&lt;/h1&gt;

&lt;p&gt;Since there can be multiple DDR memory chips connected to the same DDR
bus each memory has a separate chip select (/CS).  When /CS is low the
chip is selected.&lt;/p&gt;

&lt;p&gt;To access a DDR memory a bank must be opened and a row selected.  This
is done by sending a Bank Activate command which is performed when /CS&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;/RAS are low and /CAS + /WE are high, with the bank and row
addresses present on BA and A.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The controller can then perform multiple reads or writes to the active
bank and row.  A read command is performed when /CS + /CAS are low and
/RAS + /WE are high.  Data is transferred a few cycles later on the DQ
lines (with DM as a data mask on writes) using DQS as a strobe.  Data
is always transferred in bursts.  For the DDR memory in the SDS7102 a
burst consists of 4 words.  If the DDR controller wants to read a
single word it transfers all 4 words in the burst but can throw away
the 3 words it doesn’t need.&lt;/p&gt;

&lt;p&gt;A write command looks almost identical to a read except that /WE is
low.  The /DM signals which are transferred in parallel to the DQ
signals are used to specify which words of a burst should be written
to memory.&lt;/p&gt;

&lt;p&gt;Its possible to perform multiple read and write commands to the same
row.  One bank select command could be followed by a read command and
then a write command.&lt;/p&gt;

&lt;p&gt;There are a few more commands in the DDR protocol. There are
configuration commands which configure parameters such as the burst
length and the CAS latency - the time from the read/write command
until the data transfer starts.  Other commands perform a refresh of a
bank or put the memory in power saving mode.  After a bank has been
opened it must be precharged using yet another special command before
it can be accessed again.  As an optimization the last read/write
command can hold A10 high indicating that the memory should perform an
automatic precharge of the bank.&lt;/p&gt;

&lt;h1 id=&quot;implementation&quot;&gt;Implementation&lt;/h1&gt;

&lt;p&gt;I wrote myself a DDR controller that simulated beautifully.  It
doesn’t really do anything, all it does is return the address of each
word as the data.  I start reading a burst of 4 16-bit words starting
at 0x20 I should get the following results back:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;0x0020 -&amp;gt; 0x0020
0x0021 -&amp;gt; 0x0021
0x0022 -&amp;gt; 0x0022
0x0023 -&amp;gt; 0x0023
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;But of course didn’t work at all when I tried it in the FPGA.  To
speed things up I tried to use the control signals (CS, RAS, CAS, WE)
directly in my test for a read operation:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;if not bus.CS_B and bus.RAS_B and not bus.CAS and not bus.WE:
    rd_active.next = 1
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;That it didn’t work is not too surprising, if the time from when the
control signals change until the positive clock edge when I use the
result is too short the combinatoric logic will not have time to
settle and I will get invalid results.&lt;/p&gt;

&lt;p&gt;With an external bus the right thing to do is to immediately store the
external values in internal registers and only use the registered
values.  This aligns the values to the internal clock and gives the
combinatoric logic a full clock cycle to settle.&lt;/p&gt;

&lt;p&gt;Unfortunately, this increases the delay before the FPGA returns the
results to the SoC.  I would see garbage in the first odd words and
after that I would see data that should have been returned by previous
reads:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;0x0020 -&amp;gt; 0x0020
0x0021 -&amp;gt; 0x3800
0x0022 -&amp;gt; 0x0022
0x0023 -&amp;gt; 0xe284
0x0024 -&amp;gt; 0x0024
0x0025 -&amp;gt; 0x0021
0x0026 -&amp;gt; 0x0026
0x0027 -&amp;gt; 0x0023
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Huh?  How can this happen?&lt;/p&gt;

&lt;p&gt;But it actually makes sense.  The DDR controller has two receive FIFOs
for each byte, one FIFO which stores data from the positive edge of
DQS and a second FIFO which stores data from the negative edge of DQS.
When the DDR controller expects the transfer to finish it reads out
the data from the FIFO.  If my emulated DDR memory is too slow the
last falling edge of DQS will be too late and reading from the
negative edge FIFO will return strange data.&lt;/p&gt;

&lt;p&gt;I finally realised that I was wasting one clock cycle in my DDR code
which I could remove.  This means that could start the data transfer
earlier and the last negative clock edge would not be too late and
data started showing up as it should on the SoC.&lt;/p&gt;

&lt;p&gt;I did have occasional problems with corrupted bits and I thought this
might be due to the DQS signal arriving too early at the SOC so I
added some Xilinx IODELAY2 primitives to delay the DQS signals
slightly and that seems to have helped.&lt;/p&gt;

&lt;p&gt;Here’s how a burst read from the FPGA looks for real:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2016-07-25-sds7102-fast-buses/real.png&quot; alt=&quot;Real DDR read transaction&quot; /&gt;&lt;/p&gt;

&lt;p&gt;I’m not sure if I have implemented all this correctly, but it does
seem to work in practice, so I’ll just nod and be happy.&lt;/p&gt;

&lt;h1 id=&quot;the-other-fast-bus&quot;&gt;The other fast bus&lt;/h1&gt;

&lt;p&gt;The other fast bus in the scope is the memory bus between the FPGA and
its own DDR memory.  This also worried me because OWON ignored the
Xilinx requirement that “RZQ is not optional”.&lt;/p&gt;

&lt;p&gt;Well, I finally tried.  I used the Xilinx core generator tool to
create a memory controller which I then added some glue code for in
MyHDL.  I did not connect the RZQ line which stopped the memory
controller from synthesizing.  But after reading the generated verilog
code a bit I found a parameter in the memory controller
“C3_CALIB_SOFT_IP” that I could set to “FALSE” which stopped it from
trying to use the RZQ line and that gave me a .bin file that I could
load into FPGA.&lt;/p&gt;

&lt;p&gt;I did, and as far as I can tell the DDR controller is actually
working.  The “calib_done” line is high indicating that the DDR
controller managed to calibrate itself and the error line from the
test bench is low indicating that it hasn’t seen any errors so far.  I
first ran the memory controller at 133Hz, but then raised the clock
frequency to 266MHz and finally 333MHz.  I also tried 400MHz but then
the error flag went on indicating that something was wrong.&lt;/p&gt;

&lt;p&gt;Even so, at 333MHz the DDR controller should be able to transfer 333 *
2 * 2 = 1333 MBytes per second during a burst.  There’s a fair bit of
overhead between each burst but this should still be enough to be able
to store 1Gsample/second from the ADC into DDR memory with a bit of
bandwidth to spare.&lt;/p&gt;

&lt;p&gt;So if the DDR controller is actually working, and I haven’t messed up
my calculations of the clock speed, and the DDR controller will be
stable at this speed, and there’s nothing else wrong, it seems that I
should actually be able to use both the fast bus to the SoC and the
DDR memory.&lt;/p&gt;

&lt;p&gt;Now I just need to tie all this together.&lt;/p&gt;

&lt;p&gt;Everything I’ve written about here is available in my &lt;a href=&quot;https://github.com/wingel/sds7102&quot;&gt;SDS7102
repository on GitHub&lt;/a&gt;.  Note that
I’ve switched to newer versions of both Buildroot and the Linux
kernel.  Take a look at README.md and run “./update.sh” if you are
using an older version from my repository.  And run “git log –stat”
to see which checkins do what and what files they affect.&lt;/p&gt;

&lt;p&gt;Update: A new post &lt;a href=&quot;/2016/08/15/sds7102-ddr-capture.html&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
</description>
        <pubDate>Mon, 25 Jul 2016 00:00:00 +0200</pubDate>
        <link>http://blog.weinigel.se/2016/07/25/sds7102-fast-buses.html</link>
        <guid isPermaLink="true">http://blog.weinigel.se/2016/07/25/sds7102-fast-buses.html</guid>
        
        <category>nerdy stuff</category>
        
        <category>sds7102</category>
        
        <category>fpga</category>
        
        <category>myhdl</category>
        
        <category>xilinx</category>
        
        
      </item>
    
      <item>
        <title>Source code for my SDS7102 port</title>
        <description>&lt;p&gt;This is a post in a series about me poking at the insides of my OWON
SDS7012 oscilloscope.  You might want to start reading at the
&lt;a href=&quot;/2016/05/01/sds7102-hacking.html&quot;&gt;beginning&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I’ve finally managed to convince myself to clean up the source code
for my Linux port and FPGA image for the SDS7102.  You can find all of
it on &lt;a href=&quot;https://github.com/wingel/sds7102&quot;&gt;GitHub&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Don’t look to closely at the MyHDL code though.  I’m still learning
and it’s ugly and you might go blind.&lt;/p&gt;

&lt;p&gt;The Linux port is a bunch of ugly hacks and quick fixes to get
something running at all on the scope.  And there are traces of my
failed attempt to make a devicetree based port left in there.&lt;/p&gt;

&lt;p&gt;I hope that the instructions in the README will be enough to get you
started, if not, just send me a mail and ask.&lt;/p&gt;

&lt;p&gt;Update: &lt;a href=&quot;/2016/07/25/sds7102-fast-buses.html&quot;&gt;I’ve done some more work on the fast buses in the scope&lt;/a&gt;.&lt;/p&gt;
</description>
        <pubDate>Sat, 16 Jul 2016 00:00:00 +0200</pubDate>
        <link>http://blog.weinigel.se/2016/07/16/sds7102-source.html</link>
        <guid isPermaLink="true">http://blog.weinigel.se/2016/07/16/sds7102-source.html</guid>
        
        <category>nerdy stuff</category>
        
        <category>sds7102</category>
        
        
      </item>
    
      <item>
        <title>The analog frontend of the SDS7102</title>
        <description>&lt;p&gt;This is a post in a series about me poking at the insides of my OWON
SDS7012 oscilloscope.  You might want to start reading at the
&lt;a href=&quot;/2016/05/01/sds7102-hacking.html&quot;&gt;beginning&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I’ve been home sick from work for a few days and have mostly been
sleeping my days away.  But now I’m awake in the middle of the night
so I thought I’d document what I did last week with the analog
frontend of the SDS7102.&lt;/p&gt;

&lt;p&gt;There won’t any other pictures in this post so I might as well start
with a gratuitous picture of my desk with my disassembled SDS7102
scope.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2016-07-15-sds7102-afe/sds7102-desk.jpg&quot; alt=&quot;SDS7102 PCB on my desk&quot; /&gt;&lt;/p&gt;

&lt;p&gt;The metal plate to the upper right is the back plate of the scope
which holds the power supply.  230V can be lethal and it’s not
something I want to risk, instead I’m powering the scope with 7.5V
from a bench power supply using two clips on the battery connector.
I’ve disconnected the display and the front panel since I don’t need
them for most of the things I’m doing.&lt;/p&gt;

&lt;p&gt;The two scope probes in the picture are connected to a scope which is
not in the picture.  The yellow cable to the upper left has a resistor
on one end with the other end connected to a sharp multi-meter probe.
Hold the resistor against ground or VCC and poke with the probe at an
undriven pin to force it high or low.&lt;/p&gt;

&lt;p&gt;I’m using a FTDI TTL-232R-3V3 cable connected to UART0 on the scope.
A bus blaster MIPS is used for JTAG.  The USBee SX is used as a logic
analyzer and is hooked up to the SPI bus.  The reason that half of the
main PCB of the scope hangs outside of the desk is that I have a scope
probe plugged into channel 1 on the bottom which then probes an audio
signal on the RCA cable.  The scope is also connected to the local
network using ethernet.&lt;/p&gt;

&lt;p&gt;All of these things are connected to a Linux PC which is not pictured.
This means that I can log into the Linux PC and do just about
everything remotely.  I can power the scope on and off using some
simple scripts that talk to the power supply over a USB port.  I can
connect to the serial console.  I can use OpenOCD to debug the CPU.  I
can use just about any audio application to create a test signal to
measure.  And I can use sigrok to record data from the logic analyzer.&lt;/p&gt;

&lt;p&gt;Or I can record data from the logic analyzer once.  The sigrok
firmware seems to be quite buggy and hangs after doing a capture and
the logic analyzer has to be unplugged to restart it.  Meh.  I guess
you can’t get everything.  It should be possible to reload the
firmware in the logic analyzer and reset it that way but I haven’t
figured out how to do that yet.&lt;/p&gt;

&lt;h1 id=&quot;the-analog-frontend&quot;&gt;The analog frontend&lt;/h1&gt;

&lt;p&gt;What I wanted to do was to figure out how all the control signals from
the SoC and the FPGA affected the signals from the frontend.  I
rebooted the scope with the OWON firmware and did a soft reset and
load Linux so the ADC would be running.&lt;/p&gt;

&lt;p&gt;I already knew that the solid state relays connected to GPA0 and GPD8
on the SoC controlled the AC/DC coupling on channel 1 and 2
respectively, so I haven’t even bothered checking that.&lt;/p&gt;

&lt;p&gt;By just looking at the captured data I quickly realised that the four
relays near the channel inputs controlled the attenuation.  They have
to be switched in pairs and in opposite directions to work.  GPA1 and
GPA15 are used for channel 1, if GPA1 is high and GPA15 is low it
selects low attenuation, if GPA1 is low and GPA15 is high it selects
high attenuation which attenuates the signal by 10 times compared to
low attenuation.  GPH12 and GPE1 do the same thing for channel 2.&lt;/p&gt;

&lt;p&gt;Each channel has a four channel MUX which is used to select a signal
from somewhere in the AFE which is fed to the trigger circuit for the
channel.  As far as I can tell three of the four inputs are all fed
exactly the same signal and the fourth input is fed something that
looks like the output of a differentiator amplifier.  I guess this
could be useful for triggering on the slope of a signal.  Having a
four channel mux to select between only two different inputs feels a
bit wasteful so I’m probably missing something.&lt;/p&gt;

&lt;p&gt;The odd relay at the top of the channel 2 shield box is connected to
GPE3 and if high switches the trigger circuits for channel two to the
external trigger input.  This means that it’s possible to trigger on
channel 1 and channel 2 at the same time, or on channel 1 and the
external trigger.  The relay close to the external trigger input
switches between low and high attenuation for the external trigger.&lt;/p&gt;

&lt;h1 id=&quot;the-spi-bus&quot;&gt;The SPI bus&lt;/h1&gt;

&lt;p&gt;The ADC, the ADF frequency synthesizer used to create the sampling
clock for the ADC, the Rohm DAC and the two LMH variable gain
attenuators for each channel are all connected to a SPI bus from the
FPGA.  I could probably have figured out the configuration for all
these devices by reading the data sheets but I was getting a bit lazy
so I soldered some wires onto the SCK and SDO wires of the SPI bus.
After a lot of curses about sigrok, it had problems keeping up with
the data rate from the logic analyzer and would often stop capturing
and also had to be unplugged after each capture, I finally managed to
capture the all data transmitted over the SPI bus when the OWON
firmware booted up.&lt;/p&gt;

&lt;p&gt;I then annotated the transfer log with information about what each
access did:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;24 bits 0x000000c9 LMH
24 bits 0x000fe100 ADF
24 bits 0x0000fa02 ADF
24 bits 0x00020000 ADF? strange value
24 bits 0x00247bc3 ADF? strange value
24 bits 0x000000c7 LMH
32 bits 0x001b89ff ADC reg 0xb -&amp;gt; 0x89ff
32 bits 0x001a007f ADC reg 0xa -&amp;gt; 0x007f
32 bits 0x001dffff ADC reg 0xd -&amp;gt; 0xffff
32 bits 0x001effff ADC reg 0xe -&amp;gt; 0xffff
24 bits 0x000000d7
32 bits 0x001389ff ADC reg 0x3 -&amp;gt; 0x89ff
32 bits 0x0012007f ADC reg 0x2 -&amp;gt; 0x007f
32 bits 0x001dffff ADC reg 0xd -&amp;gt; 0xffff
32 bits 0x001effff ADC reg 0xe -&amp;gt; 0xffff
24 bits 0x00107681 ADF? strange value
24 bits 0x000000c7 LMH
32 bits 0x001b89ff ADC reg 0xb -&amp;gt; 0x89ff
32 bits 0x001a007f ADC reg 0xa -&amp;gt; 0x007f
32 bits 0x001dffff ADC reg 0xd -&amp;gt; 0xffff
32 bits 0x001effff ADC reg 0xe -&amp;gt; 0xffff
24 bits 0x000000d7 LMH
32 bits 0x001389ff ADC reg 0x3 -&amp;gt; 0x89ff
32 bits 0x0012007f ADC reg 0x2 -&amp;gt; 0x007f
32 bits 0x001dffff ADC reg 0xd -&amp;gt; 0xffff
32 bits 0x001effff ADC reg 0xe -&amp;gt; 0xffff
14 bits 0x000012a5 DAC reg 2 -&amp;gt; 0x295
24 bits 0x00247bc3 ADF? strange value
14 bits 0x000012a5 DAC reg 2 -&amp;gt; 0x295
14 bits 0x000012a5 DAC reg 2 -&amp;gt; 0x295
14 bits 0x000012a5 DAC reg 2 -&amp;gt; 0x295
24 bits 0x000000c7 LMH
32 bits 0x001b89ff ADC reg 0xb -&amp;gt; 0x89ff
32 bits 0x001a007f ADC reg 0xa -&amp;gt; 0x007f
32 bits 0x001dffff ADC reg 0xd -&amp;gt; 0xffff
32 bits 0x001effff ADC reg 0xe -&amp;gt; 0xffff
24 bits 0x000000d7 LMH
32 bits 0x001389ff ADC reg 0x3 -&amp;gt; 0x89ff
32 bits 0x0012007f ADC reg 0x2 -&amp;gt; 0x007f
32 bits 0x001dffff ADC reg 0xd -&amp;gt; 0xffff
32 bits 0x001effff ADC reg 0xe -&amp;gt; 0xffff
14 bits 0x000012a5 DAC reg 2 -&amp;gt; 0x295
14 bits 0x000012a5 DAC reg 2 -&amp;gt; 0x295
14 bits 0x000012a5 DAC reg 2 -&amp;gt; 0x295
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;I had not hooked up the logic analyzer to any of the chip select or
load/latch signals of the chips so I had to guess a bit about what
each transfer did.  From reading the data sheet I knew that the Rohm
DAC wanted 14 bits of data for each write so those were easy to
identify.  The ADC wanted 32 bits and those were also easy.&lt;/p&gt;

&lt;p&gt;Unfortunately the two LMH6518 variable gain amplifiers and the ADF4360
frequency synthesizer all wanted 24 bits and they were harder to tell
apart.  But the high 9 bits of the LMH6518 data should be 0 so all the
transfers of 0xc9, 0xc7 and 0xd7 were probably to them and the rest
would be to the ADF4360.&lt;/p&gt;

&lt;p&gt;I wrote my own SPI master for the FPGA which could be controlled from
the SoC.  The first thing I did was to talk to the Rohm DAC.  It was
fairly easy to realize that the bit order of the Rohm DAC was reversed
so I had to reverse all the data I transferred to it.  Why OWON did
things this way I don’t know since the Rohm DAC has a pin which
controls the bit order and they could just have set it the other way.
I guess they did it that way do begin with and couldn’t be bothered to
change it.  Anyway, writing 0x100 to channel one of the Rohm DAC
produced an output voltage on AO1 of almost exactly 1V, 0x200 produced
2V and so on up to 0x3ff which produced 4V.&lt;/p&gt;

&lt;p&gt;Since I could see the frequency from the ADF frequency synthesizer on
with my edge counter on the FPGA I tried to configure it next.  It
didn’t work.  I realized that it latched on a different edge so I
changed my SPI master to work with it and finally managed to change
the clock frequency.&lt;/p&gt;

&lt;p&gt;I then went on to the LMH variable gain amplifiers and saw that the
amplitude of the signals from the ADC changed when I changed their
configuration.&lt;/p&gt;

&lt;p&gt;Finally I managed to configure the ADC.  The simplest change I did was
to switch between sampling one scope channel with both channels of the
ADCs and sampling one scope channel with each channel.  This was easy
to see in the data captured from the ADC.&lt;/p&gt;

&lt;p&gt;Then I tried to put all this together and of course nothing worked.
Sometimes I could change the configuration for the LMH amplifiers,
sometimes not and sometimes the ADF frequency synthesizer just went
mad.  I spent a lot of time wondering if my SPI master was subtly
broken.  In the end I realised that the different chips all have
different requirements on clock polarity (CPOL) and which edge data
should be changed on (CPHA) and how the chip select or load/latch
signals should look.  &lt;a href=&quot;http://dlnware.com/theory/SPI-Transfer-Modes&quot;&gt;Read here for more on the different SPI
transfer modes&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I rewrote the SPI master completely so that it had some configuration
bits for CPOL, CPHA and the chip select or latch/load signals which
could be set per transfer and this finally made everything work.  I
could finally change the gain of the LMH amplifiers, switch the
frequency of the ADF frequency synthesizer and change the ADC
configuration.  Success.&lt;/p&gt;

&lt;p&gt;I also found out that the first write of 0x000000c9 wasn’t to the LMH,
it’s to the ADF frequency synthesizer.  And that some of the writes
I’ve labeled as ADF don’t look correct, the values are just strange.&lt;/p&gt;

&lt;p&gt;Then the ADC stopped working.  After spending too much time looking at
my FPGA code I found out that the wire I had soldered from the logic
analyzer to a resistor from the SCK line to the ADC had pulled so hard
on the joint that it had broken the surface mount resistor.  Oops.
Luckily enough it was easy to desolder the resistor and replace it
with a new one.&lt;/p&gt;

&lt;h1 id=&quot;back-to-the-analog-frontend&quot;&gt;Back to the analog frontend&lt;/h1&gt;

&lt;p&gt;The outputs from the two trigger MUXes are fed to the two inputs on a
dual PECL comparator.  After probing around a bit I could see that AO1
and AO2 from the Rohm DAC ended up as the reference voltages for the
comparators.  So that’s how you set the trigger voltage.  Nice.&lt;/p&gt;

&lt;p&gt;I was unable to find out what the other channels from the DAC are used
for.  Most strangely, none of them affect the vertical position of the
signal in the captured data from the ADC.  Something is not right
here.&lt;/p&gt;

&lt;p&gt;After a lot of head scratching I realised that the mystery chip U16
had a couple of analog voltages on pin 3 and 4.  It must be a DAC of
some kind.  When I searched for D32E+DAC I immediately found the page
for the &lt;a href=&quot;http://www.ti.com/product/DAC8532&quot;&gt;TI DAC8532 16-Bit, Dual Channel, DAC&lt;/a&gt; and the pinout matched.  Why on
earth didn’t I find it before?  I must have been blind.&lt;/p&gt;

&lt;p&gt;This also explains the writes of strange values to the ADF4360 in the
SPI transaction log above.  They were writes to the DAC8532.  After
forcing the chip select pin of the DAC8532 low and looking for edges
on the FPGA I found that it was connected to pin A8 on the FPGA.  I
added support for the new chip select in my SPI master and, yes!  I
was able to change the output voltage of the DAC and this also changed
the vertical position of the signals from the two scope channels.
Probing the U3 OP-AMP below the DAC8532 is used to scale/shift the
voltage and increase the drive capability of the DAC.  That’s one
mystery solved.&lt;/p&gt;

&lt;p&gt;As far as I can tell only 2 out of the 10 outputs of the Rohm DAC are
used.  This seems a bit wasteful, why not use a cheaper 2 channel DAC
of only 2 channels are needed.  I must be missing something here too.&lt;/p&gt;

&lt;p&gt;Where were I?  Yes, triggering.  When looking at a 3 kHz sine wave on
channel 1 I could see that pin D5 on the FPGA was changing at 3kHz.
Doing the same thing for channel 2 would make T12 switch.  The strange
thing is that this signal did not come from the PECL comparator.  If I
changed the trigger voltage from the Rohm DAC so that the PECL output
would stop switching I still saw 3kHz on the FPGA pins.  Huh?&lt;/p&gt;

&lt;p&gt;I had all forgotten about the Rohm BA7046 video SYNC separators.
There were two chips, U34 and U35, one for each channel that I hadn’t
figured out what they were doing yet.  It turns out that they are also
fed from the trigger MUXes for each channel.  And when a BA7046 is fed
a 3kHz sine wave it will output a 3kHz square wave on the vertical
sync (Vd) output and a 17-18kHz square wave on the horizontal sync
(Hd) output.  And those were the 3kHz seen on D5 and T12 and 17-18kHz
seen on R12 and E6.&lt;/p&gt;

&lt;p&gt;But what about the PECL comparators?  I had located all but four pins
on the FPGA and those pins were not differential pairs so they could
not be fed the signals from the PECL comparator directly.  I could not
find any other ICs that could do a PECL to single ended conversion and
none of those remaining four pins on the FPGA were toggling anyway.
How can this work?&lt;/p&gt;

&lt;p&gt;I went to bed a bit frustrated.  I was this close to knowing
everything there is to know about how the scope is put together and
was stumped on this last little tiny thing.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;http://www.catb.org/jargon/html/story-of-mel.html&quot;&gt;When the light went on it nearly blinded
me&lt;/a&gt;.  I had messed
up the location of a FPGA pin before, hadn’t I?  What if I had done it
more than once?  What if the to differential outputs from the PECL
comparator were actually connected to two differential inputs of the
FPGA?  The PECL signal levels are so high that both a low and high
PECL level will look like a high signal to the FPGA and they have to
be measured differentially to show any toggling.&lt;/p&gt;

&lt;p&gt;Of course.  I had put down B6 as the pin for the SDA line to the
AT88SC CryptoMemory.  It was actually part of the B6/A6 pair connected
to the channel 1 differential output from the PECL comparator and
C9/A9 was connected to the channel 2 output.  The AT88SC SDA was
actually on C5.  With this change I could finally see 3kHz on both
pairs of pins on the FPGA and could make them stop switching by
changing the trigger voltages from the Rohm DAC.  Doh.&lt;/p&gt;

&lt;h1 id=&quot;success&quot;&gt;Success?&lt;/h1&gt;

&lt;p&gt;I’m done.&lt;/p&gt;

&lt;p&gt;I have discovered almost everything there is to find out about the
scope.  I know what every single pin on the FPGA does and what most of
pins on the SoC do.  I know how the analog frontend works, I can
control the attenuation, gain, levels, triggers and sampling frequency
and can capture samples from the ADC.&lt;/p&gt;

&lt;p&gt;Ok, there are few things such as why they are using a 10 channel DAC
or 4 channel MUX when they only use 2 channels.  There is one pin,
GPB6, on the SoC that keeps changing every now and then and I have no
idea why it does that.&lt;/p&gt;

&lt;p&gt;This means that I’ve probably missed something.&lt;/p&gt;

&lt;p&gt;But I know enough about how the scope fits together that I should be
able to actually write a new firmware for it from scratch.  It may be
hard to get the fast DDR bus between the SoC and FPGA to work, and it
may be hard to make the DDR memory connected to the FPGA work since it
breaks the Xilinx MIG design rules, but it should be possible.&lt;/p&gt;

&lt;p&gt;There’s a big risk that I won’t do that though.  Most of the the fun
with this has been to figure out how everything works.  To actually
write a polished GUI for a scope and all the small pieces that are
needed isn’t quite as fun.&lt;/p&gt;

&lt;p&gt;But I’m going on vacation in august and if it rains a lot I might get
a bit bored.  Who knows. :-)&lt;/p&gt;

&lt;p&gt;Update: &lt;a href=&quot;/2016/07/16/sds7102-source.html&quot;&gt;I’ve put the source code for my Linux port and FPGA image on
github&lt;/a&gt;.&lt;/p&gt;
</description>
        <pubDate>Fri, 15 Jul 2016 00:00:00 +0200</pubDate>
        <link>http://blog.weinigel.se/2016/07/15/sds7102-afe.html</link>
        <guid isPermaLink="true">http://blog.weinigel.se/2016/07/15/sds7102-afe.html</guid>
        
        <category>nerdy stuff</category>
        
        <category>sds7102</category>
        
        <category>fpga</category>
        
        
      </item>
    
      <item>
        <title>The SoC bus on the SDS7102</title>
        <description>&lt;p&gt;This is a post in a series about me poking at the insides of my OWON
SDS7012 oscilloscope.  You might want to start reading at the
&lt;a href=&quot;/2016/05/01/sds7102-hacking.html&quot;&gt;beginning&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;In my &lt;a href=&quot;/2016/07/08/sds7102-capture.html&quot;&gt;last post&lt;/a&gt; I
had managed to capture some data from the ADC and make a bit of sense
of it.  Since I now had a way of capturing data in the FPGA, why not
apply it to the pins connected to the SoC?&lt;/p&gt;

&lt;p&gt;As I had discovered earlier the FPGA is connected to the DDR2 memory
bus of the SoC.  I believe that the FPGA simulates a DDR2 memory.
This is bad because the DDR2 bus is rather complicated.&lt;/p&gt;

&lt;p&gt;The good thing is that I should be able to write some code on the SoC
that affects the signals on the SoC bus which should make it easier to
figure out which pins do what.  For the ADC bus where I had to make do
with whatever the ADC gave me, now I can control the data.&lt;/p&gt;

&lt;p&gt;The DDR2 memory chip has the following signals:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;/CS - chip select (active low)&lt;/li&gt;
  &lt;li&gt;/RAS - row address strobe (active low)&lt;/li&gt;
  &lt;li&gt;/CAS - column address strobe (active low)&lt;/li&gt;
  &lt;li&gt;/WE - write enable (active low)&lt;/li&gt;
  &lt;li&gt;BA0..BA1 - bank address&lt;/li&gt;
  &lt;li&gt;A0..A12 - address&lt;/li&gt;
  &lt;li&gt;DQS0..1 - data strobe&lt;/li&gt;
  &lt;li&gt;DM0..1 - data mask&lt;/li&gt;
  &lt;li&gt;DQ0..15 - data&lt;/li&gt;
  &lt;li&gt;CKE - clock enable&lt;/li&gt;
  &lt;li&gt;ODT - on die termination&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Adding all these together makes a total of 41 signals.  I had only
identified 38 signals for the SoC bus so something is missing.  I
guess CKE and ODT are optional, but there’s still one signal too many.&lt;/p&gt;

&lt;p&gt;Oh well, lets get started.&lt;/p&gt;

&lt;h1 id=&quot;data-signals&quot;&gt;Data signals&lt;/h1&gt;

&lt;p&gt;The first thing to do was to write some test code for the SoC which
would run directly after starting a capture of the SoC pins:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;for (i = 0; i &amp;lt; 32; i++)
    writel((1&amp;lt;&amp;lt;i), soc_mem + i * 4);
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The idea is to move a 1 bit across a 32 bit word and see if I can find
that pattern in the capture.  “soc_mem” is the address assigned to the
second DDR2 memory connected to the SoC.&lt;/p&gt;

&lt;p&gt;Here’s a capture of those signals:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2016-07-10-sds7102-soc-bus/soc-signals.png&quot; alt=&quot;Signals on the SoC bus&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Looks kind of messy doesn’t it?&lt;/p&gt;

&lt;p&gt;But if I just move the signals around a bit and give them sensible
names it looks a lot better:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2016-07-10-sds7102-soc-bus/soc-data.png&quot; alt=&quot;Data signals on the SoC bus&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Yes, that’s clearly a 1 bit moving through the data signals.  Since
DDR2 bus is only has 16 data signals the pattern repeats twice.  The
other signals are control signals or address signals.&lt;/p&gt;

&lt;p&gt;It’s interesting that the SoC sometimes turns off the data signals
when it’s done with them and sometimes not.  Or maybe the SoC has just
three-stated the data signals and the voltage hasn’t dropped yet.  It
shouldn’t matter that much though.&lt;/p&gt;

&lt;p&gt;I later realised that the numbering of the DQ signals is wrong, the
CPU does write buffering and reordering of writes which means that the
writes are not in made the same order as done from my test code.  But
that doesn’t matter too much, for the moment all I need to know are
which pins are the data signals, I can always sort out the exact
ordering later.&lt;/p&gt;

&lt;h1 id=&quot;address-signals&quot;&gt;Address signals&lt;/h1&gt;

&lt;p&gt;I should be able to do something similar for the address signals if I
change the stride for the address.  Since the writes are buffered and
can change order I decided to use reads instead:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;for (i = 0; i &amp;lt; 64; i++)
    readb(ddr_mem + i * 2);
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;I then tried this for lots of different strides for the address.  At
first I couldn’t make A0 toggle and finally realized that that’s
because the DDR2 memory bus always does burst transfers and the CPU
seems to align those transfers to 32 bit words.  With a 16 bit memory
this means that the lowest address bit will never change.&lt;/p&gt;

&lt;p&gt;After a lot of tries and thinking I ended up with this code:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;for (i = 0; i &amp;lt; 64; i++)
    readb(ddr_mem + i * (2048 + 64 * 2048));
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;My capture buffer in the FPGA was just large enough to capture 64
memory accesses so that’s how many loops I have.&lt;/p&gt;

&lt;p&gt;The settings for the memory controller in the SoC look like this:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;RASBW1 = 2'b10 -&amp;gt; 13 bit
CASBW1 = 2'b10 -&amp;gt; 10 bit
ADDRCFG1 = 2'b01 -&amp;gt; {RA,BA,CA}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This tells us that the row address (RAS) is 13 bits wide, the column
address (CA) is 10 bits wide, and that the order those addresses are
pasted together to make up the memory address on the SoC.  The bank
address (BA) is hardcoded to two bits.&lt;/p&gt;

&lt;p&gt;With a 16 bit wide memory and 10 bits of column addresses a stride of
2048 should avoid toggling the column addresses.  The low bank address
signal (BA0) should toggle for every access.  The high bank address
signal (BA1) should toggle for every other access.  With 64 steps the
loop will change 6 bits so the next 4 address signals A0, A1, A2 and
A3 should also toggle.  And if I add one more stride of 64 * 2048 at
the same time I should be able to also toggle the next 6 row address
lines: A4 to A9.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2016-07-10-sds7102-soc-bus/soc-address.png&quot; alt=&quot;Address signals on the SoC bus&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Well, look at that.&lt;/p&gt;

&lt;p&gt;BA0 is toggling for every read.  BA1 is toggling at half that rate.
A0 is used for both the row address and the column address so that
wire just shows short spikes when it’s writing the row address before
it switches to writing the column address which is always zero.  But
the length of the bursts of spikes on A0 is twice as long as the
pulses on BA1.  It continues this way all up to A9 as expected.&lt;/p&gt;

&lt;p&gt;A10 is toggling all the time even though my code shouldn’t touch that
address signal.  But A10 has an alternate function, it tells the DDR2
memory to perform an automatic precharge so that’s probably why it is
toggling.  A11 is low here since it’s never touched by my code, but I
have verified that it will also change if I change the stride to
128 * 64 * 2048.&lt;/p&gt;

&lt;p&gt;It also seems that the OWON designers dropped the highest address line
A12 from the SoC bus.  I guess they can manage with only 32MBytes of
address space for the SoC bus.&lt;/p&gt;

&lt;h1 id=&quot;control-signals&quot;&gt;Control signals&lt;/h1&gt;

&lt;p&gt;Only eight signals left.&lt;/p&gt;

&lt;p&gt;Quoting a &lt;a href=&quot;https://www.skhynix.com/static/filedata/fileDownload.do?seq=215&quot;&gt;Hynix document about DDR2 memory&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;The Bank Activate command is issued by holding /CAS and /WE HIGH with
/CS and /RAS LOW at the rising edge of the clock. The bank addresses
BA0 ~ BA2 are used to select the desired bank. The row address A0
through A15 is used to determine which row to activate in the
selected bank.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;blockquote&gt;
  &lt;p&gt;After a bank has been activated, a read or write cycle can be
executed. This is accomplished by setting /RAS HIGH, /CS and /CAS LOW
at the clock’s rising edge.  /WE must also be defined at this time to
determine whether the access cycle is a read operation (/WE HIGH) or
a write operation (/WE LOW).&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Lets go back to the writes again and zoom in on a single transaction:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2016-07-10-sds7102-soc-bus/soc-control.png&quot; alt=&quot;Control signals on the SoC bus&quot; /&gt;&lt;/p&gt;

&lt;p&gt;That pretty much looks like it, doesn’t it?  /CS and /RAS go low with
the BA and A lines low for a Bank Activate command for a row address
of 0 in bank 0.  After that /CS, /CAS and /WE go low to start a write;
A3 and A4 are high saying that the write should be to column address&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;The SoC then starts another write, this time A2 is also high
indicating a write to address 28.  A10 is also high asking for the
second write the DDR memory to do an automatic precharge of the bank
when the write has finished.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Each write command is for a burst of 4 words and a few cycles after
the writes we can see the transfer of the data words with data being
transferred twice as fast as the control signals.  That’s what the
double in DDR, Double Data Rate, means.&lt;/p&gt;

&lt;p&gt;The data signals first transfer the low 16 bits of a 32 bit word,
where the lowest bit is set, followed by the high 16 bits of a 32 bit
word which are zero.  The next two transfers are the low 16 bit word
with the second lowest bit set followed by the high 16 bit word which
is zero.  This finishes the first burst of 4 words of 16 bits each.
It’s immediately followed by the next second burst of 4 more words.&lt;/p&gt;

&lt;p&gt;Note that this is the first write on the bus but the address is 24 not
0 as I would have expected.  For some reason the SoC reorderd the
writes.  This means that my first naive numbering of the DQ lines is
wrong, but now that I know the address signals I can use those to fix
up the numbering of the DQ signals.  I’ll do that sometime later.&lt;/p&gt;

&lt;p&gt;I think that DM0 and DM1 are the data masks indicating which bytes of
each word should be written, DM0 says that the low byte of a 16 bit
word should be written and DM1 says that the high byte should be
written.  Since my code is writing full 32 bits words, both bytes
should be written in the waveform above.  Note that I might have
swapped the order of the bytes for DM0 and DM1 but that should be easy
to fix if that’s the case.&lt;/p&gt;

&lt;p&gt;I’ve named the last two signals DQS0 and DQS1.  They are supposed to
toggle for each bit that’s transmitted and as you can see they are low
all the time in my capture.  I have seen them change sporadically in
other transactions so I think they actually are the DQS signals but
I’m not totally sure.&lt;/p&gt;

&lt;p&gt;The problem is that on a DDR2 bus DQS are the byte strobes that should
be used to sample the low or high 8 bits of the DM and DQ signals.  In
my capture I’m capturing everything using the DDR reference clock
which is not how you’re supposed to do it, and I’m probably seeing
corrupted data.&lt;/p&gt;

&lt;h1 id=&quot;finale&quot;&gt;Finale&lt;/h1&gt;

&lt;p&gt;I think I have identified all signals on the SoC bus now.  It’s
definitely the DDR2 memory bus and yes, the FPGA is supposed to
emulate a second DDR2 memory chip connected to the SoC.  Ouch.&lt;/p&gt;

&lt;p&gt;The reason for “ouch” is that a DDR2 memory bus is a bit complicated.
A FPGA emulation of a DDR2 memory can ignore most things it doesn’t
need such as bank precharge or refresh, but the clocking is really
messy.  A 16 bit wide DDR2 bus works with three different clock
domains, there’s the main DDR clock which is used for the control
signals, but then you really have to treat the two DQS signals as
clock for the corresponding DM and DQ signals.&lt;/p&gt;

&lt;p&gt;There are reasons why the Xilinx Spartan 6 has a hardcoded DDR2 memory
controller.  It’s tricky to get this right and even trickier to make
it go fast.  Obviously OWON has managed to make this work so it’s not
impossible, but I’m not sure if I can manage it.&lt;/p&gt;

&lt;p&gt;I guess we’ll see.  At least I don’t have to worry about not having
anything to learn in my spare time. :-)&lt;/p&gt;

&lt;p&gt;Update: A new &lt;a href=&quot;/2016/07/15/sds7102-afe.html&quot;&gt;post&lt;/a&gt; is up.&lt;/p&gt;
</description>
        <pubDate>Sun, 10 Jul 2016 00:00:00 +0200</pubDate>
        <link>http://blog.weinigel.se/2016/07/10/sds7102-soc-bus.html</link>
        <guid isPermaLink="true">http://blog.weinigel.se/2016/07/10/sds7102-soc-bus.html</guid>
        
        <category>nerdy stuff</category>
        
        <category>sds7102</category>
        
        <category>fpga</category>
        
        <category>myhdl</category>
        
        
      </item>
    
      <item>
        <title>Capturing data from the SDS7102 ADC</title>
        <description>&lt;p&gt;This is a post in a series about me poking at the insides of my OWON
SDS7012 oscilloscope.  You might want to start reading at the
&lt;a href=&quot;/2016/05/01/sds7102-hacking.html&quot;&gt;beginning&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I have spent a few weeks learning &lt;a href=&quot;http://myhdl.org/&quot;&gt;MyHDL&lt;/a&gt; and
building FPGA modules that use the &lt;a href=&quot;https://en.wikipedia.org/wiki/Wishbone_(computer_bus)&quot;&gt;Wishbone
bus&lt;/a&gt; to talk to
each other.&lt;/p&gt;

&lt;p&gt;The first thing I built was a SPI slave which uses the three wires
from the FPGA configuration interface that can be used after
configuration.  A normal SPI interface uses four wires, an active low
Chip Select (/CS), Serial ClocK (SCK), Serial Data Out (SDO) and
Serial Data In (SDI).  SDO is often called Master Out Slave In (MOSI)
and SDI is often called Master In Slave Out (MISO).  Anyway, with only
three wires I had to combine SDO and SDI into one bidirectional wire
SDIO.  I tried to modify Linux spi-gpio driver to use a bidirectional
SDIO pin but couldn’t see a simple way to do it so I hacked together a
dumb Linux driver to do it instead.&lt;/p&gt;

&lt;p&gt;The SPI slave is a Wishbone bus master and can control a Wishbone
slave connected to it.  I converted my MyHDL code that counted edges
on pins to a Wishbone slave and made that work with the master.  That
was fairly easy.  The hard part was to write a bus multiplexer which
allowed me to connect multiple Wishbone slaves to one Wishbone master.
MyHDL doesn’t always work as I expect it to and has a lot of
limitations so it took me a few weeks to make all of that work.&lt;/p&gt;

&lt;p&gt;With that done it was fairly easy to create a dual port block RAM in
the FPGA where one port is connected to a Wishbone slave so that the
SoC can read out the data from the block RAM.  The other port is
connected to the ADC.  Using the the ADC clock it captures and stores
the 32 differential ADC signals until the RAM becomes full.  Another
Wishbone slave with a single one bit register is used to stop and
restart the capture.&lt;/p&gt;

&lt;h1 id=&quot;first-capture&quot;&gt;First capture&lt;/h1&gt;

&lt;p&gt;I used &lt;a href=&quot;http://sox.sourceforge.net/&quot;&gt;sox&lt;/a&gt; on a Linux PC to play a 3kHz
sine wave which I then probed with channel one on the scope:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;play -n synth 0 sin 3000
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;I booted the scope with the OWON firmware and made sure that it showed
a sine wave on the screen.  I then did a soft reset of the CPU in the
SoC and loaded Linux.  Doing a soft reset leaves the ADC running and
this is how I discovered which pins were connected to the ADC to begin
with.  I could also see that the ADC clock was running at 100MHz so I
modified the capture code in the FPGA to only save every 100th sample
to block RAM giving a final sampling rate of 1Msamples/second.&lt;/p&gt;

&lt;p&gt;The signals from the ADC consists of 32 differential signals from the
ADC and a differential lcok.  The 32 signals are split up into four
sets of 8 bits each.  The ADC can sample one channel and interleave
all four sets or sample two channels and interleave two sets per
channel. As a starting point I assigned the signals to the four sets
in the same order they appeared in the FPGA data sheet.&lt;/p&gt;

&lt;p&gt;This is a plot of the data from the capture.  I have saved the raw
data
&lt;a href=&quot;/files/2016-07-08-sds7102-capture/capture.txt&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2016-07-08-sds7102-capture/capture-original.png&quot; alt=&quot;Original Capture&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Red, green, blue and magenta are the four different sets of 8 bits
each.  It doesn’t look much like a sine wave, but that’s to be
expected, having the FPGA signal order match order they are connected
the ADC would have been astonishingly lucky.&lt;/p&gt;

&lt;p&gt;There are patterns in the signal through.  A 3kHz sine wave sampled at
1Msamples/second and with 1000 samples in the plot means that the
signal should repeat three times in the capture, and it does.&lt;/p&gt;

&lt;p&gt;The data mostly looks like random noise but it flattens out every now
and then.  And there are some things in the middle of the noise, that
could be sine wave shaped if I squint a bit and wish really hard.&lt;/p&gt;

&lt;h1 id=&quot;positive-or-negative&quot;&gt;Positive or negative&lt;/h1&gt;

&lt;p&gt;After thinking a bit I realised that when I booted Linux some of the
GPIOs changed which in turn switched the attenuation relays in the
analog frontend (AFE) to a different range.  This makes the signal
swing higher and the flat parts is when the signal goes beyond the
limits of the ADC and is clipped to all ones.&lt;/p&gt;

&lt;p&gt;And after thinking a bit more I realised that the clipping was a good
thing.  Each differential signal consists of two wires with opposite
polarity.  When routing the wires it is sometimes easier to connect
the positive pin from the transmitter to the negative pin on the
receiver and vice versa.  This makes the PCB layout simpler but means
that the polarity of the signal will be inverted at the receiver.  But
since the signal is clipped and there should have been long stretches
with all ones in the capture data, all bits that were zero in those
stretches must be inverted.&lt;/p&gt;

&lt;p&gt;Invert those signals in the decoder and plot the capture data again.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2016-07-08-sds7102-capture/capture-inverted.png&quot; alt=&quot;Capture with some signals inverted&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Now the flat regions are all ones and the regions in between actually
show some signs of having sine waves in them.&lt;/p&gt;

&lt;h1 id=&quot;the-power-of-statistics&quot;&gt;The power of statistics&lt;/h1&gt;

&lt;p&gt;What can I do next?  I had a feeling that the lower valued bits of
each 8 bit set of signals will change more often than the higher
valued ones, so why not collect some statistics.&lt;/p&gt;

&lt;p&gt;Sort the signals by the number of transitions and group them four by
four.&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Transitions&lt;/th&gt;
      &lt;th&gt;Signals&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;6&lt;/td&gt;
      &lt;td&gt;E13 F12 J14 K15&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;18&lt;/td&gt;
      &lt;td&gt;F10 H13 H15 L14&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;42&lt;/td&gt;
      &lt;td&gt;D11 G12 G14 M15&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;84&lt;/td&gt;
      &lt;td&gt;C11 F15 J13 N14&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;173&lt;/td&gt;
      &lt;td&gt;P15 B14 E15 J11&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;335 - 357&lt;/td&gt;
      &lt;td&gt;K12 B15 M13 D14&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;358 - 368&lt;/td&gt;
      &lt;td&gt;B10 R15 T14 C13&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;406 - 427&lt;/td&gt;
      &lt;td&gt;L12 R14 C15 B12&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;A sine wave which repeats 3 times will change the highest valued bit
twice for every repetition which means 2 * 3 = 6 transitions which
matches the first line.  The other lines are multiples of three all
the way up to 173 which is one less than 58 * 3 = 174; the signal is
probably a bit off center compared to the full range.  The number of
transitions for the lowest three bits varies so they might be mixed up
and some of these transitions are probably just noise.&lt;/p&gt;

&lt;p&gt;Rearrange the bits to match the table above and reanalyze the capture
data:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2016-07-08-sds7102-capture/capture-transitions.png&quot; alt=&quot;Capture with signals sorted by number of transitions&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Whoa!  That definitely looks like a clipped sine wave.  There is a lot
of noise but it’s starting to look decent.&lt;/p&gt;

&lt;p&gt;Hmm, what next.  Since all sets of bits contain similar data the ADC
has to be in the mode where it interleaves all four sets for channel&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;Let’s reboot the scope with the OWON firmware and turn on both
channel 1 and channel 2 with the same source signal.  Now two of the
sets should be interleaved for channel 1 and the other two sets should
be interleaved for channel 2.  Back to Linux again and make a new
capture:&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;img src=&quot;/images/2016-07-08-sds7102-capture/capture-split.png&quot; alt=&quot;Capture with two channels&quot; /&gt;&lt;/p&gt;

&lt;p&gt;The bits for the two channels seem to be a bit mixed up, so let’s try
the trick of finding parts of the signal that are clipped signal and
swap around bits between sets until the first two sets are all ones.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2016-07-08-sds7102-capture/capture-split-ones.png&quot; alt=&quot;Capture with two channels reordered by clipping&quot; /&gt;&lt;/p&gt;

&lt;p&gt;That looks a lot better, the red and green sets are for channel 1, the
blue and magenta sets are for channel 2.  There’s an obvious mix-up
between the green and blue sets.  Between samples 80 and 230 the value
of the blue samples are 128 too high and the green samples are 128 too
low.  Swap the bits with value 128 between the sets.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2016-07-08-sds7102-capture/capture-split-ones-fixed.png&quot; alt=&quot;Capture with two channels with an obvious fix&quot; /&gt;&lt;/p&gt;

&lt;p&gt;This is starting to look decent.  There are still some artefacts down
at the three lowest valued bits but it’s not too bad.  I just ended up
swapping the low valued bits around a bit randomly until the plots
looked decent.  This is that I finally ended up with:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2016-07-08-sds7102-capture/capture-final.png&quot; alt=&quot;Final Capture&quot; /&gt;&lt;/p&gt;

&lt;p&gt;And if I increase the attenuation a bit it looks like this:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2016-07-08-sds7102-capture/capture-attenuation.png&quot; alt=&quot;Final Capture with higher attenuation&quot; /&gt;&lt;/p&gt;

&lt;p&gt;I think I’m good, each set looks decent now.  I might still have
messed up the order between the sets, what I think is set 1 of channel
1 might actually be set 2 from channel 1.  I’ll have to play around a
bit with higher frequency signals later and see if I can figure out if
the order is correct or not.&lt;/p&gt;

&lt;p&gt;But I can finally use the scope to capture signals, whee.  My work is done for today.&lt;/p&gt;

&lt;p&gt;Update: A new &lt;a href=&quot;/2016/07/10/sds7102-soc-bus.html&quot;&gt;post&lt;/a&gt; is up.&lt;/p&gt;
</description>
        <pubDate>Fri, 08 Jul 2016 00:00:00 +0200</pubDate>
        <link>http://blog.weinigel.se/2016/07/08/sds7102-capture.html</link>
        <guid isPermaLink="true">http://blog.weinigel.se/2016/07/08/sds7102-capture.html</guid>
        
        <category>nerdy stuff</category>
        
        <category>sds7102</category>
        
        <category>fpga</category>
        
        <category>myhdl</category>
        
        
      </item>
    
      <item>
        <title>SDS7102 Front Panel</title>
        <description>&lt;p&gt;This is a post in a series about me poking at the insides of my OWON
SDS7012 oscilloscope.  You might want to start reading at the
&lt;a href=&quot;/2016/05/01/sds7102-hacking.html&quot;&gt;beginning&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I lied.  I had material for one more post about the front panel so I
might as well write something about it before I forget what I was
doing.&lt;/p&gt;

&lt;p&gt;In previous posts I had discovered that &lt;a href=&quot;/2016/05/28/sds7102-gio-pins.html&quot;&gt;pins GPF3, GPH3 from the
SoC&lt;/a&gt; and &lt;a href=&quot;/2016/06/19/sds7102-more-fpga-pins.html&quot;&gt;pins A5,
B5, B8, C6, and D6 from the
FPGA&lt;/a&gt; were
connected to the front panel connector.&lt;/p&gt;

&lt;h1 id=&quot;keys&quot;&gt;Keys&lt;/h1&gt;

&lt;p&gt;The contributor “lemon” on the EEVblog forums had taken some nice
photos of the &lt;a href=&quot;http://www.eevblog.com/forum/testgear/review-of-owon-sds7102/msg293559/#msg293559&quot;&gt;Front panel
PCBs&lt;/a&gt;.
Here’s a zoomed in image of the active circuitry on the front panel.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2016-06-21-sds7102-front-panel/front-panel-circuitry.jpg&quot; alt=&quot;Front panel circuitry&quot; /&gt;&lt;/p&gt;

&lt;p&gt;U31 is a &lt;a href=&quot;http://www.nxp.com/documents/data_sheet/74HC_HCT393.pdf&quot;&gt;74HC393 Dual 4-bit binary ripple
counter&lt;/a&gt;.  U7
and U26 are &lt;a href=&quot;http://www.nxp.com/documents/data_sheet/74HC_HCT4051.pdf&quot;&gt;74HC4051 8-channel analog
multiplexer/demultiplexers&lt;/a&gt;.
The traces are clearly visible from the image and even without seeing
the traces on the back it is possible to deduce how the front panel
works.  Here’s how everything is connected:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2016-06-21-sds7102-front-panel/front-panel-schematic.png&quot; alt=&quot;Front panel schematic&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Simple and sweet.&lt;/p&gt;

&lt;p&gt;The keys are arranged in a matrix.  (In the schematic that is, the
physical layout of the keys is different - some keys are even on a
separate PCB).  Each key consists of a a switch which can connect a
column to a row through a diode.  Some of the keys are actually rotary
encoders, but at this level they can be seen as simple keys.&lt;/p&gt;

&lt;p&gt;The three low bits of the first counter control mux U7 and selects a
column (Y0-Y7) that will be connected to FPGA pin B5 through the
common pin (Z).&lt;/p&gt;

&lt;p&gt;The three low bits of the second counter control mux U26 and selects a
row (Y0-Y7) that will be shorted to ground through the common pin (Z).&lt;/p&gt;

&lt;p&gt;Normally all columns are pulled high by resistors R1-R8.  If a key is
pressed and the row it is connected to is shorted to ground, the whole
column will be pulled low.  If that column is selected, the output to
the FPGA will be the same as the state of the column.&lt;/p&gt;

&lt;p&gt;FPGA pin C6 outputs a 312.5kHz clock which increments the first
counter.  When the first counter wraps around it increments the second
counter.  By toggling FPGA pin C6 and reading FPGA pin B5 it’s
possible to read the state of all 8x8 = 64 keys.  FPGA pin
A5 is used connected to the Master Reset (MR) pins of the counters so
that it can restart the counters from zero.&lt;/p&gt;

&lt;h1 id=&quot;bits-and-bobs&quot;&gt;Bits and bobs&lt;/h1&gt;

&lt;p&gt;The remaining pins of the front panel connector control the lights in
the buttons on front panel.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;FPGA pin B8 controls the white LED in the Single button.
FPGA pin D6 controls the red LED in the Run/Stop button.
SOC pin GPF3 controls the green LED in the Run/Stop button.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;I have no idea why they decided to use a SoC pin for the green LED and
why the other two LEDs are controlled by the FPGA.  It probably made
sense at the time.&lt;/p&gt;

&lt;p&gt;This still leaves one pin unaccounted for.  SoC pin GPH3 is also
routed to the front panel connector.  I could not find that pin
anywhere on the front panel.&lt;/p&gt;

&lt;p&gt;But there’s something that looks like a small window for an infrared
receiver on the front of the scope.  And there’s space for some more
components and through hole mount component with three pins on the PCB
just beind the window.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2016-06-21-sds7102-front-panel/front-panel-infrared.jpg&quot; alt=&quot;Front panel infrared&quot; /&gt;&lt;/p&gt;

&lt;p&gt;SoC pin GPH3 is the receive pin for serial port 1 and the UARTs do
support IrDA.  Doh!  Of course!&lt;/p&gt;

&lt;p&gt;I haven’t actually verified that GPH3 ends up here and I can’t get to
the pads on the board without unscrewing the whole panel which I’d
prefer not to do, but I bet that’s it.  I guess someone at OWON had
plans for adding an infared remote control to the scope but that the
plans were scrapped and they just decided to just not mount the
components for it.&lt;/p&gt;

&lt;p&gt;Update: A new &lt;a href=&quot;/2016/07/08/sds7102-capture.html&quot;&gt;post&lt;/a&gt; is up.&lt;/p&gt;

</description>
        <pubDate>Tue, 21 Jun 2016 00:00:00 +0200</pubDate>
        <link>http://blog.weinigel.se/2016/06/21/sds7102-front-panel.html</link>
        <guid isPermaLink="true">http://blog.weinigel.se/2016/06/21/sds7102-front-panel.html</guid>
        
        <category>nerdy stuff</category>
        
        <category>sds7102</category>
        
        <category>fpga</category>
        
        
      </item>
    
  </channel>
</rss>
