Skip to content

Commit

Permalink
feat: add xnor-popcount based mac bin impl
Browse files Browse the repository at this point in the history
  • Loading branch information
glencoe committed Oct 2, 2023
1 parent ed3086c commit 6a63eb3
Show file tree
Hide file tree
Showing 7 changed files with 244 additions and 0 deletions.
Empty file.
49 changes: 49 additions & 0 deletions elasticai/creator/nn/binary/mac/_mac_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import pytest
import torch

from elasticai.creator.file_generation.on_disk_path import OnDiskPath
from elasticai.creator.vhdl.ghdl_simulation import GHDLSimulation

from .layer import MacLayer

sw_function_test_data = [
((1, 1, 1), (-1, 1, -1), -1),
((1, -1, -1, 1, 1), (1, -1, 1, -1, 1), 1),
]


@pytest.mark.parametrize(["x1", "x2", "expected"], sw_function_test_data)
def test_sw_function(x1, x2, expected):
x1 = torch.tensor((1, 1, 1), dtype=torch.float32)
x2 = torch.tensor((-1, 1, -1), dtype=torch.float32)
mac = MacLayer(vector_width=x1.shape[0])
expected = -1
y = mac(x1, x2).item()
assert expected == y


test_data = [
# 00010 * 00010 -> 00000 00100 -> 000(00 001)00 -> 00001
((1, 1, 1), (1, 1, 1)),
# 00010 * 01000 -> 00000 10000 -> 000(00 100)00 -> 00100
((-1, 1), (-1, -1)),
((-1, -1, 1), (1, 1, 1)),
]


@pytest.mark.simulation
@pytest.mark.parametrize(["x1", "x2"], test_data)
def test_mac_hw_for_integers(tmp_path, x1, x2):
root_dir_path = str(tmp_path)
root = OnDiskPath("main", parent=root_dir_path)
mac = MacLayer(vector_width=len(x1))
test_bench_name = "testbench_fxp_mac"
y = mac(torch.tensor(x1), torch.tensor(x2)).item()
testbench = mac.create_testbench(test_bench_name)
testbench.set_inputs(x1=x1, x2=x2)
testbench.save_to(root)
runner = GHDLSimulation(workdir=f"{root_dir_path}", top_design=testbench)
runner.initialize()
runner.run()
actual = testbench.parse_reported_content(runner.getReportedContent())
assert y == actual
40 changes: 40 additions & 0 deletions elasticai/creator/nn/binary/mac/bin_mac.vhd
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
library IEEE;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;

entity $name is
port (
reset : in std_logic;
next_sample : in std_logic;
x1 : in std_logic_vector($total_width-1 downto 0);
x2 : in std_logic_vector($total_width-1 downto 0);
sum : out std_logic := '0';
done : out std_logic := '0'
);
end;

architecture rtl of $name is
function popcount(x: std_logic_vector($total_width-1 downto 0)) return integer is
variable num_ones : natural := 0;
variable result : integer range -${total_width} to ${total_width} := 0;
begin
for i in x'range loop
if x(i) = '1' then
num_ones := num_ones + 1;
end if;
end loop;
result := 2*num_ones - $total_width;
return result;
end function;

function is_positive(x: integer) return std_logic is
variable result : std_logic := '1';
begin
if x < 0 then
result := '0';
end if;
return result;
end function;
begin
sum <= is_positive(popcount(x1 xnor x2));
end rtl;
19 changes: 19 additions & 0 deletions elasticai/creator/nn/binary/mac/design.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
from elasticai.creator.file_generation.savable import Path, Savable
from elasticai.creator.file_generation.template import InProjectTemplate


class MacDesign(Savable):
def __init__(self, name: str, vector_width: int):
self._name = name
self._vector_width = vector_width

def save_to(self, destination: Path) -> None:
core_component = InProjectTemplate(
package="elasticai.creator.nn.binary.mac",
file_name="bin_mac.vhd",
parameters={
"total_width": str(self._vector_width),
"name": self._name,
},
)
destination.create_subpath("bin_mac").as_file(".vhd").write(core_component)
26 changes: 26 additions & 0 deletions elasticai/creator/nn/binary/mac/layer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
from fixed_point.number_converter import FXPParams

from elasticai.creator.file_generation.savable import Savable

from .._math_operations import MathOperations
from .design import MacDesign
from .mactestbench import MacTestBench


class MacLayer:
def __init__(self, vector_width: int):
self.ops = MathOperations()
self._vector_width = vector_width

def __call__(self, a, b):
return self.ops.matmul(a, b)

def create_design(self, name: str) -> Savable:
return MacDesign(vector_width=self._vector_width, name=name)

def create_testbench(self, name: str) -> MacTestBench:
return MacTestBench(
uut=self.create_design("mac_wrapper_test"),
uut_name="mac_wrapper_test",
name=name,
)
54 changes: 54 additions & 0 deletions elasticai/creator/nn/binary/mac/mactestbench.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
from elasticai.creator.file_generation.savable import Path
from elasticai.creator.file_generation.template import InProjectTemplate


class MacTestBench:
def __init__(self, uut, name, uut_name):
self._uut = uut
self._uut_name = uut_name
self._inputs = None
self._destination = None
self.name = name

def set_inputs(self, **inputs: dict[str, tuple[float, ...]]):
self._inputs = inputs["x1"], inputs["x2"]

def parse_reported_content(self, outputs: list[str]):
return 2 * int(outputs[0]) - 1

@property
def _width(self) -> str:
return str(len(self._inputs[0]))

def save_to(self, destination: Path):
self._destination = destination
inputs = self._prepare_inputs_for_test_bench(self._inputs)
test_bench = InProjectTemplate(
package="elasticai.creator.nn.binary.mac",
file_name="testbench.tpl.vhd",
parameters=inputs
| {
"uut_name": self._uut_name,
"name": self.name,
"total_width": self._width,
},
)
self._uut.save_to(destination)
destination.create_subpath(self.name).as_file(".vhd").write(test_bench)

def _prepare_inputs_for_test_bench(self, inputs):
x1, x2 = inputs

def zero_one(xs):
return ["0" if x < 0 else "1" for x in xs]

def to_string(xs):
return '"{}"'.format("".join(xs))

x1 = zero_one(x1)
x2 = zero_one(x2)
inputs = {
"x1": to_string(x1),
"x2": to_string(x2),
}
return inputs
56 changes: 56 additions & 0 deletions elasticai/creator/nn/binary/mac/testbench.tpl.vhd
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
use std.textio.all;
use ieee.std_logic_textio.all;
use std.env.finish;


entity $name is

end;

architecture Behavioral of $name is
constant TOTAL_WIDTH : integer := $total_width;
constant total_clock_cycles: integer := 4;
signal clock_period : time := 2 ps;
signal clock : std_logic := '0';
signal reset : std_logic := '0';
subtype input_t is std_logic_vector(TOTAL_WIDTH-1 downto 0);
signal next_sample : std_logic;
signal x1 : input_t := (others => '0');
signal x2 : input_t := (others => '0');
signal sum : std_logic;
signal done : std_logic;

signal x1_values : input_t := $x1;
signal x2_values : input_t := $x2;



begin
UUT : entity work.${uut_name}
port map (reset => reset, next_sample => next_sample, x1 => x1, x2 => x2, sum => sum, done => done);

next_sample <= clock;
clock <= not clock after clock_period/2;

testbench_1 : process(clock, done)
variable iteration_id : integer := 0;
variable reset_performed : std_logic := '0';
variable value_id : integer := 0;
constant max_iterations : integer := 5;
begin
if rising_edge(clock) then
if iteration_id = 0 then
x1 <= x1_values;
x2 <= x2_values;
else
report to_string(sum);
finish;
end if;
iteration_id := iteration_id + 1;
end if;
end process;

end Behavioral;

0 comments on commit 6a63eb3

Please sign in to comment.