Friday, December 27, 2024

Kansas Lava to Simulate Circuits

Kansas Lava is a Haskell library that provides abstractions and powerful combinations to describe and simulate hardware circuits. Hardware descriptions are strongly typed and provide a means to describe hardware in a clear manner and express values that flow between components. You can describe both sequential and combinatorial circuits using Kansas Lava. To install the same on Fedora 20 (x86_64), follow the steps described below:

Download petersen/cabal-install repo from https://copr.fedoraproject.org/coprs/petersen/cabal-install/. If you do not have wget installed on your system, install it first using the following command:

 [stextbox id=”grey”]# yum install wget[/stextbox]

- Advertisement -

You can then run the following from /etc/yum.repos.d directory:

 [stextbox id=”grey”]$ wget https://copr.fedoraproject.org/
coprs/petersen/cabal-install/repo/
fedora-20-i386/petersen-cabal-install-
fedora-20-i386.repo[/stextbox]

- Advertisement -

Install cabal-install and Git.

 [stextbox id=”grey”]# yum install cabal-install git[/stextbox]

Download petersen/ghc-7.8 repo from https://copr.fedoraproject.org/coprs/petersen/ghc-7.8/ to /etc/yum.repos.d using:

 [stextbox id=”grey”]# wget https://copr.fedoraproject.org/
coprs/petersen/ghc-7.8/repo/fedora-
rawhide-i386/petersen-ghc-7.8-fedora-
rawhide-i386.repo[/stextbox]

Install Glasgow Haskell compiler (GHC) using:

 [stextbox id=”grey”]# yum install ghc[/stextbox]

Update the cabal local package list and install sized types using:

 [stextbox id=”grey”]$ cabal update
$ cabal install sized-types[/stextbox]

Obtain the latest Kansas Lava code from GitHub using:

 [stextbox id=”grey”]$ git clone https://github.com/ku-fpg/
kansas-lava.git[/stextbox]

Enter the following into the checked out Kansas Lava directory, compile and install the same as follows:

 [stextbox id=”grey”]$ cabal install –only-dependencies
$ cabal build
$ cabal install[/stextbox]

The tool can help verify the behaviour of a circuit in simulation, generate VHDL for synthesis and also validate whether the generated VHDL is the same as the simulated circuit.

A simple code for the half adder combinatorial circuit is given below:

 [stextbox id=”grey”]

import Language.KansasLava

halfAdd :: Signal i Bool -> Signal i
Bool -> (Signal i Bool, Signal i Bool)
halfAdd a b = (carry, sum)
where carry = a `and2` b
sum = a `xor2` b

[/stextbox]

Execute the above example using Glasgow Haskell compiler (GHCi):

 [stextbox id=”grey”]$ ghci halfAdd.hs
ghci> halfAdd low high
(low,high)
ghci> halfAdd high high
(high,low)[/stextbox]

Kansas Lava implements the idea of an undefined Signal, where binary functions like and2, or2 and xor2 are short circuited. With unknown values, these function as hardware gates.

 [stextbox id=”grey”]

ghci> :m + Language.KansasLava

ghci> undefinedS `or2` low
?
ghci> undefinedS `or2` high
high
ghci> undefinedS `and2` low
low
ghci> undefinedS `and2` high
?
ghci> undefinedS `xor2` low
?
ghci> undefinedS `xor2` high
?

[/stextbox]

mux is a function that can be used to select an input. With low input, it picks the first argument in the tuple, and with high input, it picks the second argument in the tuple.

 [stextbox id=”grey”]

ghci> :m + Language.KansasLava

ghci> mux low (0,1) :: Signal c Int
0
ghci> mux high (0,1) :: Signal c Int
1

[/stextbox]

Kansas Lava provides a number of instance declarations and overloadings for the signal module.

 [stextbox id=”grey”]

instance (Num a, Rep a) => Num (Signal
i a)

instance (Bounded a, Rep a) => Bounded
(Signal i a)

instance (Show a, Bits a, Rep a) => Bits
(Signal i a)

[/stextbox]

The overloading enables you to use Haskell expressions with Signal c and can be helpful in describing hardware signals.

 [stextbox id=”grey”]

ghci> :m + Language.KansasLava

ghci> 2 :: Signal c Int
2
ghci> 2 * 4 :: Signal c Int
8

ghci> :m + Data.Bits
ghci> 8 `shiftR` 2 :: Signal c Int
2

[/stextbox]

The clock class can change a signal with time and is used for sequential values. It provides two important functions: register and delay. The register function accepts an initial value and an input sequence as arguments, while delay function is similar to register function with the initial value undefined. Register function behaves like D edge-triggered flip-flop with an internal clock. toS function converts a set of values into a signal. For example,

 [stextbox id=”grey”]

ghci> :m + Language.KansasLava

ghci> let xs = toS [1..5] :: Seq Int
ghci> xs
1 | 2 | 3 | 4 | 5 | ? .
ghci> delay xs
? | 1 | 2 | 3 | 4 | 5 | ? .
ghci> register 10 xs
10 | 1 | 2 | 3 | 4 | 5 | ? .

[/stextbox]

Kansas Lava has support for both fixed-width signed and unsigned numbers. An unsigned X4 is a 4-bit unsigned number.

 [stextbox id=”grey”]

ghci> :m + Data.Sized.Matrix
ghci> :m + Data.Sized.Unsigned

ghci> 100 :: U4
4

ghci> :m + Data.Sized.Signed

ghci> 100 :: S8
100
ghci> 100 + 100 :: S8
-56
ghci> [minBound..maxBound] :: [U4]
[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15]

[/stextbox]

It also provides support for fixed-width matrices. A matrix with four elements of boolean type is represented by Matrix U2 bool. These are useful when specifying irregular-sized values in hardware.

 [stextbox id=”grey”]

ghci> :m + Data.Sized.Matrix

ghci> let m = matrix [1,2,3,4] ::
Matrix U2 Int

ghci> :m + Data.Array.IArray

ghci> m ! 1
2
ghci> m ! 0
1

[/stextbox]

The pack utility provides a means to combine multiple signals into a single signal, and the unpack combinator does the reverse. These allow flexible-type representation of data in the program. For example,

 [stextbox id=”grey”]

ghci> :m + Language.KansasLava

ghci> let p = toS (cycle [False, True])
:: Seq Bool
ghci> let q = toS [1..] :: Seq Int
ghci> let pq = pack (p, q) :: Seq (Bool,
Int)
ghci> takeS 2 pq
(low,1) | (high,2) | (?,?) .

[/stextbox]

Apart from signal types, circuits can also be built from Haskell functions.

Consider a ROM that stores the cube of a value. funMapXXX function when used directly is similar to an asynchronous ROM.

 [stextbox id=”grey”]

ghci> :m + Language.KansasLava
ghci> :set -XScopedTypeVariables

ghci> let cubeROM = funMapXXX(\(x::Int)
-> return(x*x*x))

ghci> takeS 5 $ cubeROM $ toS [1..] ::
Seq Int
1 | 8 | 27 | 64 | 125 | ? .

[/stextbox]

Kansas Lava also provides various coerce functions that can be used between two signal types, as long as both types have the same bit-width. For example,

 [stextbox id=”grey”]

ghci> :m + Language.KansasLava

ghci> let a = toS (cycle [False, True])
:: Seq Bool
ghci> takeS 5 a
low | high | low | high | low | ? .

ghci> takeS 5 $ (unsigned) a :: Seq Int
0 | 1 | 0 | 1 | 0 | ? .

[/stextbox]

Circuits can be debugged using probes, which present a way to observe intermediate values in shallow embedded circuits without modifying the circuit interface. These are inserted in the function using probeS function.

 [stextbox id=”grey”]

ghci> :m + Language.KansasLava

ghci> setProbesAsTrace (appendFile
“DEBUG”)
ghci> let xs = toS (cycle [False, True])
:: Seq Bool
ghci> let ys = probeS “ys” (takeS 5 $
(unsigned) xs :: Seq Int)
ghci> takeS 5 ys
0 | 1 | 0 | 1 | 0 | ? .
ghci> :!cat DEBUG
ys(0)0
ys(1)1
ys(2)0
ys(3)1
ys(4)0

[/stextbox]

An important purpose of Kansas Lava is to produce useful VHDL. Hardware descriptions are converted into efficient VHDL. Kansas Lava programs are extracted into a Kansas Lava entity graph (KLEG), which is an abstract representation of the netlist. This can be optimised and written into VHDL. The generic netlist rendering tool can also target Verilog. Kansas Lava can also generate and execute a VHDL test bench. mkTestbench function can be used to generate a test bench.

The code is listed below:

 [stextbox id=”grey”]

import Language.KansasLava
import Language.KansasLava.Fabric
import Language.KansasLava.VHDL

halfAdd :: Signal i Bool -> Signal i
Bool -> (Signal i Bool, Signal i Bool)
halfAdd a b = (carry, sum)
where carry = a `and2` b
sum = a `xor2` b

fabric :: Fabric ()
fabric = do
i0 i1 let (c,s) = halfAdd i0 i1
outStdLogic “carry” c
outStdLogic “sum” s

main = do
k writeVhdlCircuit “halfAdd” “halfAdd.
vhd” k
mkTestbench “halfAdd” “.” k

The above code can be compiled and run using:

$ ghc –make halfAdd.hs

$ ./halfAdd

[/stextbox]

Following files are created in the directory where halfAdd is executed: halfAdd.vhd, halfAdd_tb.vhd and halfAdd.do. halfAdd.vhd contains the VHDL implementation of the circuit, halfAdd_tb.vhd contains testbench VHDL and halfAdd.do contains a ModelSim script to run the test bench.

halfAdd.vhd file is listed below:

 [stextbox id=”grey”]library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.NUMERIC_STD.ALL;
use work.lava.all;
use work.all;
entity halfAdd is
port(clk : in std_logic;
i0 : in std_logic;
i1 : in std_logic;
carry : out std_logic;
sum : out std_logic);
end entity halfAdd;
architecture str of halfAdd is
signal sig_6_o0 : std_logic;
signal sig_7_o0 : std_logic;
signal sig_2_o0 : std_logic;
signal sig_3_o0 : std_logic;
signal sig_5_o0 : std_logic;
signal sig_4_o0 : std_logic;
begin
sig_6_o0 <= sig_7_o0;
sig_7_o0 <= (sig_4_o0 xor sig_5_o0);
sig_2_o0 <= sig_3_o0;
sig_3_o0 <= (sig_4_o0 and sig_5_o0);
sig_5_o0 <= i1;
sig_4_o0 <= i0;
carry <= sig_2_o0;
sum <= sig_6_o0;
end architecture str; [/stextbox]

halfAdd_tb.vhd file is listed below:

 [stextbox id=”grey”]

library ieee;
use ieee.std_logic_1164.all;
use ieee.std_logic_textio.all;
library std;
use std.textio.all;
library work;
entity halfAdd_tb is
begin
end entity halfAdd_tb;
architecture sim of halfAdd_tb is
signal clk :
std_logic := ‘1’;
signal rst : std_logic := ‘0’;
constant input_size : integer := 16;
constant output_size : integer := 16;
signal input : std_logic_vector(3 downto 0):= (others => ‘0’);
signal output : std_logic_vector(3
downto 0);
begin
runtest: process is
FILE halfAdd_input : TEXT open read_
mode IS “halfAdd.in.tbf”;
FILE halfAdd_output : TEXT open write_
mode IS “halfAdd.out.tbf”;
VARIABLE line_in,line_out : LINE;
variable input_var : std_logic_vector(3
downto 0);
variable output_var : std_logic_
vector(3 downto 0);
variable needs_rst : boolean := false;
begin
while not endfile (halfAdd_input) loop
READLINE(halfAdd_input, line_in);
READ(line_in,input_var);
clk <= ‘1’;
wait for 5 ns;
input <= input_var;
if needs_rst then
rst <= ‘1’;
end if;
output(3 downto 2) <= input_var(3
downto 2);
wait for 20 ns;
clk <= ‘0’;
wait for 20 ns;
if needs_rst then
rst <= ‘0’;
needs_rst := false;
end if; output_var := output;
WRITE(line_out, output_var);
WRITELINE(halfAdd_output, line_out);
wait for 5 ns;
end loop;
wait;
end process;
dut: entity work.halfAdd
port map (
clk => clk,
i1 => input(2),
i0 => input(3),
sum => output(0),
carry => output(1)
);

end architecture sim;

[/stextbox]

halfAdd.do file is listed below:

 [stextbox id=”grey”]vlib mywork
vcom -work mywork Lava.vhd
if [catch {vcom -work mywork halfAdd.
vhd} einfo] {
puts $einfo
} else {
vcom -work mywork halfAdd_tb.vhd
vsim -lib mywork halfAdd_tb
add wave -r /*
run -all
}
quit[/stextbox]

Download source code: click here


Shakthi Kannan is MS in information technology from Rochester Institute of Technology, Rochester, New York, USA. Currently, he is working as senior R&D engineer at Manufacturing System Insights, Chennai. He is a software enthusiast who blogs at shakthimaan.com

SHARE YOUR THOUGHTS & COMMENTS

EFY Prime

Unique DIY Projects

Truly Innovative Electronics

Electronics News

Latest DIY Videos

Electronics Components

Electronics Jobs

Calculators For Electronics