[Haskell-cafe] Using Haskell to describe computer hardware.

Serguey Zefirov sergueyz at gmail.com
Mon May 9 15:58:30 CEST 2011

http://thesz.mskhug.ru/svn/hhdl/ - main repository and
http://thesz.mskhug.ru/svn/hhdl/examples/Simple.hs - three simple examples and
http://thesz.mskhug.ru/svn/hhdl/MIPS-example/ - an attempt to describe
MIPS-alike CPU using Haskell. Not yet done, it passes only simplest of
tests (it fetches commands), but if you take a look at it you'll get
an idea of what is going on.

The goal is to create something along the lines of Bluespec, but as a
DSeL in Haskell. Right now I concentrate on single-clock circuits, as
they comprise most of hardware logic.

I hacked together transformation of not-so-complex Haskell function
definition into Haskell functions that operate on infinite streams. Of
course, I tried my best to support calling other functions,
conditional operators, pattern matching, etc, because that's what
makes Haskell great and Bluespec has them too.

Below is little explanation of three simple examples.

First comes very simple function:
$(transform [d|
    f1 :: (ToWires a, Num a) => (a,a) -> a
    f1 (a,b) = a + b

After $(transform ...) you will have two functions: f1 and f1_S, the
second one operates on streams. ToWires class in the context ensure
that input and output values can be converted into arrays of bits when
we'll get that part ready.

The type of f1_S is:
*Simple> :t f1_S
  :: (ToWires a[a9GC], Num a[a9GC]) =>
     (S a[a9GC], S a[a9GC]) -> (S a[a9GC], S [String])

or put simply:
*Simple> :t f1_S
f1_S  :: (ToWires a, Num a) => (S a, S a) -> (S a, S [String])

It accepts two streams and return two streams - value and logs.

We could add messages into logs:

$(transform [d|
    f2 :: (ToWires a, Num a) => (a,a) -> a
    f2 (a,b) = s
            s = a + b
            _display_a = a
            _display_b = b
            _display_s = s

f2 is just like f1, but when ran, it will produce logs about execution
along the values. f2 used by runningSum, which compute a sum of values
seen before current cycle:

$(transform [d|
    runningSum :: (ToWires a, Num a) => a -> a
    runningSum x = currentSum
            nextSum = f2 (x,currentSum)
            currentSum = register 0 nextSum
            _display_currentSum = currentSum
            _display_x = x
            _display_nextSum = nextSum

runningSum uses latches. It latches nextSum to pass the value to the
next cycle using "register" function. "register" accepts default value
and a value to latch (stream of values to delay) .

When the time will come for synthesizing circuits, our register's will
become actual latches with reset and clk wires.

This is how we test our runningSum_S with simple data:
runningSumLogs :: IO ()
runningSumLogs = do
        mapM putStrLn $ map unlines $ take 5 $ toList_S $
	        snd $ runningSum_S (fromList_S [1::Int ..])
        return ()

The output will be like the following:
currentSum: 0
x: 1
nextSum: 1
nextSum_0 = Simple.f2 (x_1, currentSum_2):
  a: 1
  b: 0
  s: 1

currentSum: 1
x: 2
nextSum: 3
nextSum_0 = Simple.f2 (x_1, currentSum_2):
  a: 2
  b: 1
  s: 3

currentSum: 3
x: 3
nextSum: 6
nextSum_0 = Simple.f2 (x_1, currentSum_2):
  a: 3
  b: 3
  s: 6

Logs are nested and after label "nextSum_0 = Simple.f2 (x_1,
currentSum_2):" we see logs from f2_S function.

The code is in QUITE EXPERIMENTAL stage, but feel free to play and comment.

More information about the Haskell-Cafe mailing list