Added signedness support for the output C code representation. Also modified the testing scripts and the chr2c.py converter accordingly and did some documentation changes (even made a small change in readme.md). Signedness support for the output python representation is TBD.

This commit is contained in:
honzastor 2021-10-09 23:45:54 +02:00
parent 152a6b1583
commit 16c1757bc3
18 changed files with 95 additions and 60 deletions

View File

@ -51,9 +51,9 @@ For more detailed description of script's usage, use help.
## CGP testing
The `chr2c.py` script converts the input CGP chromosome generated by ArithsGen to the corresponding C code and prints it to standard output.
It is a modified version of a script originally authored by Vojtech Mrazek. Note that it required Python2 to run!
It is a modified version of a script originally authored by Vojtech Mrazek.
### Usage
```bash
python2 chr2c.py input.chr > output.c
python3 chr2c.py input.chr > output.c
```

View File

@ -11,15 +11,14 @@ from ariths_gen.core.arithmetic_circuits.general_circuit import GeneralCircuit
class ArithmeticCircuit(GeneralCircuit):
"""Class represents a general arithmetic circuit and ensures their generation to various representations.
"""Class represents a general arithmetic circuit and ensures its generation to various representations.
The __init__ method fills some mandatory attributes concerning arithmetic circuit
that are later used for generation into various representations.
"""
def __init__(self, a, b, prefix: str, name: str, out_N: int, inner_component: bool = False, one_bit_circuit: bool = False):
super().__init__(prefix, name, out_N, inner_component, inputs=[a, b])
def __init__(self, a, b, prefix: str, name: str, out_N: int, inner_component: bool = False, one_bit_circuit: bool = False, signed: bool = False):
super().__init__(prefix, name, out_N, inner_component, inputs=[a, b], signed=signed)
if one_bit_circuit is False:
if prefix == "":
self.prefix = name
@ -40,7 +39,7 @@ class ArithmeticCircuit(GeneralCircuit):
self.b = Bus(prefix=f"{b.prefix}", wires_list=b.bus)
# N output wires for given circuit
self.out = Bus(self.prefix+"_out", out_N, out_bus=True)
self.out = Bus(self.prefix+"_out", out_N, out_bus=True, signed=self.signed)
""" C CODE GENERATION """
def get_prototype_c(self):

View File

@ -10,13 +10,13 @@ from ariths_gen.wire_components import (
from io import StringIO
class GeneralCircuit():
"""Class represents a general arithmetic circuit and ensures their generation to various representations.
"""Class represents a general circuit and ensures its generation to various representations.
The __init__ method fills some mandatory attributes concerning arithmetic circuit
that are later used for generation into various representations.
"""
def __init__(self, prefix: str, name: str, out_N: int, inner_component: bool = False, inputs: list=[]):
def __init__(self, prefix: str, name: str, out_N: int, inner_component: bool = False, inputs: list=[], signed: bool = False):
if prefix == "":
self.prefix = name
else:
@ -29,6 +29,7 @@ class GeneralCircuit():
self.circuit_wires = []
self.circuit_gates = []
self.c_data_type = "uint64_t"
self.signed = signed
self.pyc = None # Python compiled function
def __call__(self, *args):
@ -242,30 +243,30 @@ class GeneralCircuit():
""" PYTHON CODE GENERATION """
# FLAT PYTHON #
def get_prototype_python(self):
"""Generates python code function header to describe corresponding arithmetic circuit's interface in python code.
"""Generates Python code function header to describe corresponding arithmetic circuit's interface in Python code.
Returns:
str: Function's name and parameters in C code.
str: Function's name and parameters in Python code.
"""
return f"def {self.prefix}(" + ", ".join([f"{x.prefix}" for x in self.inputs]) + ")" + ":" + "\n"
def get_init_python_flat(self):
"""Generates flat C code initialization and assignment of corresponding arithmetic circuit's input/output wires.
"""Generates flat Python code initialization and assignment of corresponding arithmetic circuit's input/output wires.
Returns:
str: Flat C code initialization of arithmetic circuit wires.
str: Flat Python code initialization of arithmetic circuit wires.
"""
return "".join([c.get_assign_python_flat() if isinstance(c, TwoInputLogicGate) else c.get_init_python_flat() for c in self.components])
def get_function_out_python_flat(self):
"""Generates flat C code assignment of corresponding arithmetic circuit's output bus wires.
"""Generates flat Python code assignment of corresponding arithmetic circuit's output bus wires.
Returns:
str: Flat C code containing output bus wires assignment.
str: Flat Python code containing output bus wires assignment.
"""
return self.out.return_bus_wires_values_python_flat()
# Generating flat C code representation of circuit
# Generating flat Python code representation of circuit
def get_python_code_flat(self, file_object):
"""Generates flat Python code representation of corresponding arithmetic circuit.
@ -335,6 +336,7 @@ class GeneralCircuit():
file_object.write(self.get_declaration_c_flat()+"\n")
file_object.write(self.get_init_c_flat()+"\n")
file_object.write(self.get_function_out_c_flat())
file_object.write(self.out.return_bus_wires_sign_extend())
file_object.write(f" return {self.out.prefix}"+";\n}")
# HIERARCHICAL C #
@ -427,6 +429,7 @@ class GeneralCircuit():
f"{self.get_declarations_c_hier()}\n" + \
f"{self.get_init_c_hier()}\n" + \
f"{self.get_function_out_c_hier()}" + \
f"{self.out.return_bus_wires_sign_extend()}" + \
f" return {self.out.prefix}"+";\n}"
# Generating hierarchical C code representation of circuit

View File

@ -195,11 +195,13 @@ class TwoInputLogicGate():
else:
return f" {self.out.name} = {self.gate_type}({self.a.get_wire_value_c_hier()}, {self.b.get_wire_value_c_hier()});\n"
""" PYTHON CODE GENERATION """
# FLAT PYTHON #
def get_assign_python_flat(self):
"""Generates C code for invocation of logical functions and subsequently provides assignment to their output.
"""Generates Python code for invocation of logical functions and subsequently provides assignment to their output.
Returns:
str: C code invocation of logical function and assignment to output.
str: Python code invocation of logical function and assignment to output.
"""
# No gate logic is generated if one of the inputs is a wire with constant value.
# I.e. either the constant or the second input wire is propagated to the output for the corresponding logic gate's logic function.
@ -524,7 +526,7 @@ class TwoInputInvertedLogicGate(TwoInputLogicGate):
Returns:
str: C code description of negated logic gate's Boolean function (with bitwise shifted inputs).
"""
return "~("+(super().get_function_c()) + ") & 0x01"
return "~("+(super().get_function_c()) + ") & 0x01ull"
""" VERILOG CODE GENERATION """
# FLAT VERILOG #
@ -584,7 +586,7 @@ class OneInputLogicGate(TwoInputLogicGate):
Returns:
str: C code description of logic gate's Boolean function (with bitwise shifted input).
"""
return f"{self.operator}({self.a.get_wire_value_c_flat()}) & 0x01"
return f"{self.operator}({self.a.get_wire_value_c_flat()}) & 0x01ull"
# HIERARCHICAL C #
def get_prototype_c_hier(self):

View File

@ -50,7 +50,7 @@ class ThreeInputOneBitCircuit(TwoInputOneBitCircuit):
output_bus_wire_names = []
[output_bus_wire_names.append(w.prefix) for w in self.out.bus]
circuit_block = self.__class__()
return "".join([f" {c.out.prefix} = ({circuit_block.prefix}({self.a.get_wire_value_c_hier()}, {self.b.get_wire_value_c_hier()}, {self.c.get_wire_value_c_hier()}) >> {output_bus_wire_names.index(c.out.prefix)}) & 0x01;\n" for c in self.components if c.disable_generation is False and c.out.prefix in output_bus_wire_names])
return "".join([f" {c.out.prefix} = ({circuit_block.prefix}({self.a.get_wire_value_c_hier()}, {self.b.get_wire_value_c_hier()}, {self.c.get_wire_value_c_hier()}) >> {output_bus_wire_names.index(c.out.prefix)}) & 0x01ull;\n" for c in self.components if c.disable_generation is False and c.out.prefix in output_bus_wire_names])
""" VERILOG CODE GENERATION """
# FLAT VERILOG #

View File

@ -66,7 +66,7 @@ class TwoInputOneBitCircuit(ArithmeticCircuit):
output_bus_wire_names = []
[output_bus_wire_names.append(w.prefix) for w in self.out.bus]
circuit_block = self.__class__()
return "".join([f" {c.out.prefix} = ({circuit_block.prefix}({self.a.get_wire_value_c_hier()}, {self.b.get_wire_value_c_hier()}) >> {output_bus_wire_names.index(c.out.prefix)}) & 0x01;\n" for c in self.components if c.disable_generation is False and c.out.prefix in output_bus_wire_names])
return "".join([f" {c.out.prefix} = ({circuit_block.prefix}({self.a.get_wire_value_c_hier()}, {self.b.get_wire_value_c_hier()}) >> {output_bus_wire_names.index(c.out.prefix)}) & 0x01ull;\n" for c in self.components if c.disable_generation is False and c.out.prefix in output_bus_wire_names])
# Self circuit hierarchical generation
def get_declaration_c_hier(self):

View File

@ -184,7 +184,7 @@ class SignedCarryLookaheadAdder(UnsignedCarryLookaheadAdder, ArithmeticCircuit):
name (str, optional): Name of signed cla. Defaults to "s_cla".
"""
def __init__(self, a: Bus, b: Bus, cla_block_size: int = 4, prefix: str = "", name: str = "s_cla", **kwargs):
super().__init__(a=a, b=b, cla_block_size=cla_block_size, prefix=prefix, name=name, **kwargs)
super().__init__(a=a, b=b, cla_block_size=cla_block_size, prefix=prefix, name=name, signed=True, **kwargs)
self.c_data_type = "int64_t"
# Additional XOR gates to ensure correct sign extension in case of sign addition

View File

@ -167,7 +167,7 @@ class SignedCarrySkipAdder(UnsignedCarrySkipAdder, ArithmeticCircuit):
name (str, optional): Name of signed cska. Defaults to "s_cska".
"""
def __init__(self, a: Bus, b: Bus, bypass_block_size: int = 4, prefix: str = "", name: str = "s_cska", **kwargs):
super().__init__(a=a, b=b, bypass_block_size=bypass_block_size, prefix=prefix, name=name, **kwargs)
super().__init__(a=a, b=b, bypass_block_size=bypass_block_size, prefix=prefix, name=name, signed=True, **kwargs)
self.c_data_type = "int64_t"
# Additional XOR gates to ensure correct sign extension in case of sign addition

View File

@ -130,7 +130,7 @@ class SignedPGRippleCarryAdder(UnsignedPGRippleCarryAdder, ArithmeticCircuit):
name (str, optional): Name of signed P/G rca. Defaults to "s_pg_rca".
"""
def __init__(self, a: Bus, b: Bus, prefix: str = "", name: str = "s_pg_rca", **kwargs):
super().__init__(a=a, b=b, prefix=prefix, name=name, **kwargs)
super().__init__(a=a, b=b, prefix=prefix, name=name, signed=True, **kwargs)
self.c_data_type = "int64_t"
# Additional XOR gates to ensure correct sign extension in case of sign addition

View File

@ -106,7 +106,7 @@ class SignedRippleCarryAdder(UnsignedRippleCarryAdder, ArithmeticCircuit):
name (str, optional): Name of signed rca. Defaults to "s_rca".
"""
def __init__(self, a: Bus, b: Bus, prefix: str = "", name: str = "s_rca", **kwargs):
super().__init__(a=a, b=b, prefix=prefix, name=name, **kwargs)
super().__init__(a=a, b=b, prefix=prefix, name=name, signed=True, **kwargs)
self.c_data_type = "int64_t"
# Additional XOR gates to ensure correct sign extension in case of sign addition

View File

@ -191,7 +191,7 @@ class SignedArrayMultiplier(MultiplierCircuit):
"""
def __init__(self, a: Bus, b: Bus, prefix: str = "", name: str = "s_arrmul", **kwargs):
self.N = max(a.N, b.N)
super().__init__(a=a, b=b, prefix=prefix, name=name, out_N=self.N*2, **kwargs)
super().__init__(a=a, b=b, prefix=prefix, name=name, out_N=self.N*2, signed=True, **kwargs)
self.c_data_type = "int64_t"
# Bus sign extension in case buses have different lengths

View File

@ -155,7 +155,7 @@ class SignedDaddaMultiplier(MultiplierCircuit):
"""
def __init__(self, a: Bus, b: Bus, prefix: str = "", name: str = "s_dadda_cla", unsigned_adder_class_name: str = UnsignedCarryLookaheadAdder, **kwargs):
self.N = max(a.N, b.N)
super().__init__(a=a, b=b, prefix=prefix, name=name, out_N=self.N*2, **kwargs)
super().__init__(a=a, b=b, prefix=prefix, name=name, out_N=self.N*2, signed=True, **kwargs)
self.c_data_type = "int64_t"
# Bus sign extension in case buses have different lengths

View File

@ -148,7 +148,7 @@ class SignedWallaceMultiplier(MultiplierCircuit):
"""
def __init__(self, a: Bus, b: Bus, prefix: str = "", name: str = "s_wallace_cla", unsigned_adder_class_name: str = UnsignedCarryLookaheadAdder, **kwargs):
self.N = max(a.N, b.N)
super().__init__(a=a, b=b, prefix=prefix, name=name, out_N=self.N*2, **kwargs)
super().__init__(a=a, b=b, prefix=prefix, name=name, out_N=self.N*2, signed=True, **kwargs)
self.c_data_type = "int64_t"
# Bus sign extension in case buses have different lengths

View File

@ -11,8 +11,9 @@ class Bus():
N (int, optional): Number of wires in the bus. Defaults to 1.
wires_list (list, optional): List of Wire objects used to clone one bus to another. Defaults to 0.
out_bus (bool, optional): Specifies whether this Bus is an output bus of some previous component. Defaults to False.
signed (bool, optional): Specifies whether this Bus should consider signed numbers or not (used for C code generation). Defaults to False.
"""
def __init__(self, prefix: str = "bus", N: int = 1, wires_list: list = None, out_bus: bool = False):
def __init__(self, prefix: str = "bus", N: int = 1, wires_list: list = None, out_bus: bool = False, signed: bool = False):
self.out_bus = out_bus
if wires_list is None:
self.prefix = prefix
@ -23,6 +24,21 @@ class Bus():
self.prefix = prefix
self.bus = wires_list
self.N = len(self.bus)
# Determine C code signedness
self.signed = signed
if self.N > 8:
self.c_var_size = 64
if signed is True:
self.c_type = "int64_t"
else:
self.c_type = "uint64_t"
else:
self.c_var_size = 8
if signed is True:
self.c_type = "int8_t"
else:
self.c_type = "uint8_t"
def is_output_bus(self):
"""Tells whether this Bus is an output bus.
@ -79,19 +95,28 @@ class Bus():
elif inserted_wire_desired_index != -1:
self.bus[bus_wire_index] = Wire(name=inner_component_out_wire.name, prefix=inner_component_out_wire.parent_bus.prefix, index=inserted_wire_index, value=inner_component_out_wire.value, parent_bus=self)
# TODO
def connect_bus(self, connecting_bus, start_connection_pos: int = 0, end_connection_pos: int = -1):
def connect_bus(self, connecting_bus: object, start_connection_pos: int = 0, end_connection_pos: int = -1):
"""Ensures connection of specified bus wires to this bus wires.
Used for connection of some inner circuit component's output bus (`connecting_bus`) wires
to the appropriate input bus (this `self` bus) wires of some other circuit.
Args:
connecting_bus (object): Specifies the connecting bus.
start_connection_pos (int, optional): Specifies the position from which to start interconnecting wires from the `connecting_bus` to this `self` bus. Defaults to 0.
end_connection_pos (int, optional): Specifies the position from which to end interconnecting wires from the `connecting_bus` to this `self` bus. Defaults to -1.
"""
if end_connection_pos == -1:
end_connection_pos = self.N
# Nakonec je potřeba napojit výstup adderu na výstup mac
[self.connect(o, connecting_bus.get_wire(o), inserted_wire_desired_index=o) for o in range(start_connection_pos, end_connection_pos)]
""" PYTHON CODE GENERATION """
def return_bus_wires_values_python_flat(self):
"""Retrieves values from bus's wires and stores them in bus's corresponding C variable at proper offset bit position in the bus for flat generation.
"""Retrieves values from bus's wires and stores them in bus's corresponding Python variable (object) at proper offset bit position in the bus for flat generation.
Returns:
str: C code for assigning wire values into bus represented in C code variable.
str: Python code for assigning wire values into bus represented in Python code variable.
"""
return "".join([f" {self.prefix} = 0\n"] + [f" {self.prefix} |= {w.return_wire_value_python_flat(offset=self.bus.index(w))}" for w in self.bus])
@ -102,10 +127,8 @@ class Bus():
Returns:
str: C code for declaration and initialization of bus name.
"""
if self.N > 8:
return f" uint64_t {self.prefix} = 0;\n"
else:
return f" uint8_t {self.prefix} = 0;\n"
return f" {self.c_type} {self.prefix} = 0;\n"
def return_bus_wires_values_c_flat(self):
"""Retrieves values from bus's wires and stores them in bus's corresponding C variable at proper offset bit position in the bus for flat generation.
@ -123,6 +146,18 @@ class Bus():
"""
return "".join([f" {self.prefix} |= {w.return_wire_value_c_hier(offset=self.bus.index(w))}" for w in self.bus])
def return_bus_wires_sign_extend(self):
"""Sign extends the bus's corresponding C variable to ensure proper C code variable signedness.
Returns:
str: C code for sign extending the bus variable wire values.
"""
if self.signed is True:
last_bus_wire = self.bus[-1]
return "".join([f" {self.prefix} |= {last_bus_wire.return_wire_value_c_flat(offset=i)}" for i in range(len(self.bus), self.c_var_size)])
else:
return ""
""" VERILOG CODE GENERATION """
def return_bus_wires_values_v_flat(self):
"""Retrieves values from bus's wires and stores them in bus's corresponding Verilog variable at proper offset bit position in the bus for flat generation.

View File

@ -26,15 +26,15 @@ class Wire():
"""
return False
""" Python CODE GENERATION """
""" PYTHON CODE GENERATION """
def return_wire_value_python_flat(self, offset: int = 0):
"""Retrieves desired bit value from wire represented in C code variable and bitwise shifts it to desired position for storing it within a bus for flat generation.
"""Retrieves desired bit value from wire represented in Python code variable (object) and bitwise shifts it to desired position for storing it within a bus for flat generation.
Args:
offset (int, optional): Used to shift wire value in order to be stored in proper location inside a bus. Defaults to 0.
Returns:
str: C code bitwise shift for storing (constant/variable) wire value at desired offset position.
str: Python code bitwise shift for storing (constant/variable) wire value at desired offset position.
"""
if self.is_const():
return f"({self.c_const}) << {offset}\n"
@ -65,9 +65,9 @@ class Wire():
# If wire is part of an input bus (where wire names are concatenated from bus prefix and their index position inside the bus in square brackets)
# then the wire value is obtained from bitwise shifting the required wire from the parent bus ('parent_bus.prefix' is the same value as 'self.prefix')
if self.name.endswith("["+str(self.index)+"]") and self.parent_bus is not None:
return f"(({self.prefix} >> {self.index}) & 0x01)"
return f"(({self.prefix} >> {self.index}) & 0x01ull)"
else:
return f"(({self.name} >> 0) & 0x01)"
return f"(({self.name} >> 0) & 0x01ull)"
def get_wire_value_c_hier(self):
"""Accesses desired bit value from wire represented in C code variable used for hierarchical generation.
@ -78,7 +78,7 @@ class Wire():
if self.is_const():
return f"({self.c_const})"
else:
return f"(({self.prefix} >> {self.index}) & 0x01)"
return f"(({self.prefix} >> {self.index}) & 0x01ull)"
def return_wire_value_c_flat(self, offset: int = 0):
"""Retrieves desired bit value from wire represented in C code variable and bitwise shifts it to desired position for storing it within a bus for flat generation.
@ -92,7 +92,7 @@ class Wire():
if self.is_const():
return f"({self.c_const}) << {offset};\n"
else:
return f"(({self.name} >> 0) & 0x01) << {offset};\n"
return f"(({self.name} >> 0) & 0x01ull) << {offset};\n"
def return_wire_value_c_hier(self, offset: int = 0):
"""Retrieves desired bit value from wire represented in C code variable and bitwise shifts it to desired position for storing it within a bus for hierarchical generation.
@ -106,7 +106,7 @@ class Wire():
if self.is_const():
return f"({self.c_const}) << {offset};\n"
else:
return f"(({self.prefix} >> {self.index}) & 0x01) << {offset};\n"
return f"(({self.prefix} >> {self.index}) & 0x01ull) << {offset};\n"
""" VERILOG CODE GENERATION """
def get_declaration_v_flat(self):
@ -290,7 +290,7 @@ class ConstantWireValue1(Wire):
self.value = 1
self.parent_bus = None
self.c_const = "0x01"
self.c_const = "0x01ull"
self.v_const = "1'b1"
self.blif_const = "vdd"
# Constant wire id for CGP generation

View File

@ -86,7 +86,7 @@ def parse_chromosome(chromosome, signed=False, function=None):
output.append(f"{dtype} {function}({dtype} a, {dtype} b) {{")
output.append(" int wa[%d];" % int(c_in/2))
output.append(" int wb[%d];" % int(c_in/2))
output.append(" uint64_t y = 0;")
output.append(f" {dtype} y = 0;")
# Export converted input assignments into C code function body
trans = {}
@ -130,6 +130,12 @@ def parse_chromosome(chromosome, signed=False, function=None):
else:
assert False
# Ensure proper sign extension if it is needed
if signed is True:
last_out_wire = trans[outs[-1]] # used for sign extension
for i in range(c_out, 64):
lines_end.append(" y |= (%s & 0x01ull) << %d; // default output" % (last_out_wire, i))
# Print final result return in C code function body and close it
lines_end.append(" return y;")
output += lines

View File

@ -10,11 +10,6 @@ int main() {
for (int i = -128; i < 128; i++){
for (int j = -128; j < 128; j++){
result = i + j;
// Calculating 2's complement in case of negative sum
if (result < 0) {
result = 512 + result;
}
assert(result == (int)CNAME(i,j));
}

View File

@ -10,11 +10,6 @@ int main() {
for (int i = -128; i < 128; i++){
for (int j = -128; j < 128; j++){
result = i * j;
// Calculating 2's complement in case of negative sum
if (result < 0) {
result = 65536 + result;
}
assert(result == (int)CNAME(i,j));
}