-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add xnor-popcount based mac bin impl
- Loading branch information
Showing
7 changed files
with
244 additions
and
0 deletions.
There are no files selected for viewing
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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, | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; |