Using Haskell to describe computer hardware.

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 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 where 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 where 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.
participants (1)
-
Serguey Zefirov