This post is about design and analysis of digital circuits using haskell as a language and clash as tool to generate digital circuits.

Main Idea

Frequency analysis can be preformed for digital circuits using only adders, multiplies and register. Intuitive reasoning to get response of single frequency from such circuitry as black box goes as. We insert Signal of complex numbers in circuitry and from resulting complex signal observe phase and amplitude. Registry in this setup is no more than rotation of phase or phase delay that depends on Nyquist frequency of input signal.

Analysis is not done in time, but directly in frequency domain. For one input frequency we get amplitude and phase of output. Approach works best for acyclic circuits, both linear (e.g. fir filter) and nonlinear circuits (e.g. frequency mixer). It can be used for linear cyclic circuits for example iir filter. Anaysis result can be presented as Bode or Nyquist plot. Experimental hacked up implementation is available at github.

Example of use

Clash compiler can be used to both design digital circuitry and analyze them using the great language Haskell. Both in time and frequency domain. Analysis in time domain is part of standard library that ships with distribution.

We can describe fir filter in language haskell for example as:

fir coeffs x = dotp coeffs (windowP x)
where
dotp as bs = sum (zipWith (*) as bs)

This description enables generating digital circuit by compiling in vhdl or verilog language and than burn this in FPGA or in ASIC.

We begin by generating fir coeficients. For example by using python library scipy. There is also haskell library dsp that can do same.

>>> from scipy import signal as s
>>> s.firwin2(101,freq=[0,0.5,0.55,1],gain=[1,1,1e-5,1e-5],nfreqs=1024).tolist()

this generates 101 fir coefficient-s with gain 1 in interval [0,0.55] and gain 1e-5 in interval [0.55,1] where interval represents Nyquist frequency. Nyquist frequency is frequency scaled from 0 to 1/2 of sampling frequency.

firCoef = -6.478374934754401e-05 :> 6.739792048055188e-05 :> 5.077367641329531e-05 :> -7.20036873194064e-05 :> -3.7245153312760294e-05 :> 7.249856664305871e-05 :> 2.2414720687380852e-05 :> -6.155684145140234e-05 :> -7.664810105984746e-06 :> 2.85804248573473e-05 :> -3.6152309490646324e-08 :> 3.86526486767599e-05 :> -1.4006284245813091e-05 :> -0.00015120836046040963 :> 7.361032727845754e-05 :> 0.0003152579566734551 :> -0.00021195524189591408 :> -0.0005277210648487832 :> 0.0004703572783638681 :> 0.0007717574734980716 :> -0.0008954627919597703 :> -0.001012627857164383 :> 0.0015349575580967327 :> 0.0011943982184525286 :> -0.002432123228258949 :> -0.0012377957439342097 :> 0.0036197818250699866 :> 0.0010392111520942885 :> -0.005114324388627113 :> -0.0004703430147278584 :> 0.00691059332253219 :> -0.0006228059544207712 :> -0.008978362677236961 :> 0.0024241985527292402 :> 0.011261032711201649 :> -0.005160469774066745 :> -0.013676935125896208 :> 0.00913292632920996 :> 0.016123357472055417 :> -0.014794308598846265 :> -0.01848307417265467 :> 0.02293937546874407 :> 0.02063285839843857 :> -0.03523405731548666 :> -0.022453185292383104 :> 0.05602454640129296 :> 0.02383815920968249 :> -0.10138553138188472 :> -0.024704631737024998 :> 0.3167115011351708 :> 0.5250045350057191 :> 0.3167115011351717 :> -0.024704631737024647 :> -0.10138553138188545 :> 0.023838159209682245 :> 0.05602454640129442 :> -0.02245318529238117 :> -0.03523405731548606 :> 0.020632858398438054 :> 0.02293937546874406 :> -0.018483074172654032 :> -0.014794308598846393 :> 0.01612335747205424 :> 0.009132926329209059 :> -0.013676935125896086 :> -0.005160469774066614 :> 0.011261032711201845 :> 0.002424198552728965 :> -0.008978362677237601 :> -0.0006228059544210945 :> 0.006910593322532333 :> -0.0004703430147276999 :> -0.005114324388627051 :> 0.0010392111520945455 :> 0.0036197818250703288 :> -0.001237795743934232 :> -0.0024321232282593064 :> 0.0011943982184523566 :> 0.0015349575580969096 :> -0.0010126278571641848 :> -0.0008954627919598041 :> 0.000771757473497906 :> 0.00047035727836372234 :> -0.0005277210648488565 :> -0.0002119552418958731 :> 0.0003152579566736044 :> 7.361032727858147e-05 :> -0.0001512083604604247 :> -1.4006284245899888e-05 :> 3.865264867673136e-05 :> -3.6152309459163916e-08 :> 2.8580424857355213e-05 :> -7.66481010600899e-06 :> -6.15568414514097e-05 :> 2.24147206873927e-05 :> 7.249856664305469e-05 :> -3.724515331277384e-05 :> -7.200368731939395e-05 :> 5.0773676413330495e-05 :> 6.739792048057118e-05 :> -6.478374934755455e-05 :> Nil

We define our circuitry as

firCircuit x = fir firCoef x

Result of calculating response of circuit firCircuit can be presented as following graph x axis represent frequency from 0 to Nyquist frequency and y is gain in dB. More details regarding ploting is available in testing code example available at github.

Details

Analysis can be precisely performed on acyclic circuits for both linear and some nonlinear systems. By applying iterations it is possible to analyze also cyclic circuits. Only restriction is that circutry is defined by input type that is both Num and Prependable. Prepend is using polymorphic version of register called prepend.

Naive idea I started to work from is, that we push inside signal of complex numbers. Addition is addition of complex numbers and similar for multiplication. This should work fine at least for linear circuits.

Cyclic circuits can be are analyzed getting more values from output stream and waiting for result to converge. It works for analyzing iir filters.

For mixing/nonlinear circuits multiplication of spectrum is needed. Multiplying two sin functions is expressed as following identity.

sin(a) * sin(b) = 1/2 (cos(a-b) - cos(a+b))

or when dealing in single frequency we get.

sin(x + a) * sin(x + b) ==
1/2 (cos(a - b) - cos(2x + a + b)) ==
1/2 (sin(a - b + pi/2) - sin(2x + a + b + pi/2))

Multiplying two signals on same frequency one with phase a and other with phase b results in signal decomposed in DC components and other with signal having double frequency.

Haskell type that holds phases on multiple harmonics can be Map Int (Complex Double) Where Int represents harmonics and Complex Double size and phase of a signal at that harmonics frequency. In general Num class is needed for such type. That are function + - and *.

acyclic circuits

What remains for such system is phase rotation. When we delay signal using register, we need to tell register how to delay input spectrum.

If circuit is acyclic, single value wrapped in type AnalyzerAcyclic is enough for exact analysis. This represents type of input stream and is type used as input and output of circuit.

data AnalyzerAcyclic s =
AnalyzerAcyclic {unAnalyzerAcyclic :: (Maybe (s -> s) , s)}

First type Maybe (s -> s) is spectrum transfer function. s -> s is function for rotating input used by registerP function is wrapped in Maybe, because instances of AnalyzerAcyclic for example fromInteger and fromRational are unable to provide better than value Nothing. For example function (+) requires forwarding valid transform function as output. Second type s is input spectrum for example Map Int (Complex Double) and represents amplitudes and phases on all harmonics frequencies.

cyclic circuits

Simple example of cyclic circuit would be

integrator sig = r where
r = registerP 0 r + sig

registerP is polymorphic version of register called prepend from class Prepenable with origin from prevous post and lack of theoretical foundations. Some care is required to avoid bottom or infinite loop that would happen using AnalyzerAcyclic.

Current solution is that stream is used as input. In output stream we wait for values to converge. Type that works for our case is.

data Analyzer s = Analyzer {unAnalyzer :: ([Maybe (s -> s)] , Signal s)}

First value [Maybe (s -> s)] is list of spectrum transfer functions, that is function that rotates input signal when register is reached. Second type Signal s is stream of spectrum. For registryP is required to retrieve spectrum transformation in breadth-first order. Traversing to spectrum rotate function s -> s in depth first order, would halt evaluation and would reach bottom.

For this type we need to implement Ǹum instance.

Example of + is.

da + db = FreqAn (zipWith (<|>) fa fb, liftA2 (+) a b) where
(fa,a) = unFreqAn da
(fb,b) = unFreqAn db

That is, spectrum transfer function is generated by merging two spectrum transfer functions one from argument da and other from db input. Addition is done trough Applicative instance of underlaying input type that needs to be instance of Applicative.

What next

Experimental library and some playground code in test.hs is available at github. It is not yet clear how to make Functor and Applicative instance of Analyzer and family. Also Prependable typclass as used here and previously to describe parallel circuits seems nice and useful, but has no theoretical foundations I am aware of and for extra only law it I came up with is broken in implementation of AnalyzerAcyclic. It is not clear what kind of nonlinear circuits beside frequency mixers are possible to analyze using such approach. There is missing bunch of utility functions and overall many questions remains open.

Fell free to reach me over git or irc at irc.freenode.com. I am hanging on channels #haskell and #clash-lang under nick ralu but you can also send mails on address starting with luka and ending with name of this domain.