271 lines
		
	
	
		
			8.8 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
			
		
		
	
	
			271 lines
		
	
	
		
			8.8 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
<?php
 | 
						|
 | 
						|
use Symfony\Component\ExpressionLanguage\ExpressionLanguage;
 | 
						|
 | 
						|
require_once "PythonUtils.php";
 | 
						|
 | 
						|
class LogicFunction implements JsonSerializable
 | 
						|
{
 | 
						|
    private static ExpressionLanguage $EXP_LANG; // expression language for linting an evaluation
 | 
						|
    private array $input_vars; // array of input variables
 | 
						|
    private string $expression; // the logic function in the bitwise Verilog-form
 | 
						|
 | 
						|
    public static function convertToVerilogBitwiseForm(string $expression): string
 | 
						|
    {
 | 
						|
        return str_replace(["/", "!", "*", "+"], ["~", "~", "&", "|"], $expression);
 | 
						|
    }
 | 
						|
 | 
						|
    public static function adaptToEL(string $expression): string
 | 
						|
    {
 | 
						|
        return str_replace(["~"], [" not "], $expression);
 | 
						|
    }
 | 
						|
 | 
						|
    public static function collectVariables(string $expression): array
 | 
						|
    {
 | 
						|
        preg_match_all("/\w/", $expression, $variables);
 | 
						|
        return array_filter(array_unique($variables[0]), fn($v) => !empty($v) && !is_numeric($v[0]));
 | 
						|
    }
 | 
						|
 | 
						|
    public function __construct(string $expression = "", array $input_vars = [])
 | 
						|
    {
 | 
						|
        $this->setExpression($expression);
 | 
						|
        $this->setInputVars(($input_vars === []) ? self::collectVariables($this->expression) : $input_vars);
 | 
						|
    }
 | 
						|
 | 
						|
    public function getTruthTable(): string
 | 
						|
    {
 | 
						|
        $N = count($this->input_vars);
 | 
						|
        if ($N == 0) {
 | 
						|
            return "";
 | 
						|
        }
 | 
						|
 | 
						|
        $M = pow(2, $N);
 | 
						|
 | 
						|
        $vars = [];
 | 
						|
        foreach ($this->input_vars as $var) {
 | 
						|
            $vars[$var] = 0;
 | 
						|
        }
 | 
						|
 | 
						|
        $expression = self::adaptToEL($this->getExpression("verilog_logic"));
 | 
						|
//        printf("Cooked: %s\n", $cooked_form);
 | 
						|
 | 
						|
        $tt = [];
 | 
						|
        for ($i = 0; $i < $M; $i++) {
 | 
						|
            for ($k = 0; $k < $N; $k++) {
 | 
						|
                $vars[$this->input_vars[$k]] = (($i >> ($N - $k - 1)) & 1) === 1;
 | 
						|
//                printf("%d ", $vars[$this->input_vars[$k]]);
 | 
						|
            }
 | 
						|
            $out = self::$EXP_LANG->evaluate($expression, $vars);
 | 
						|
            $out_str = $out ? "1" : "0";
 | 
						|
//            printf("%d\n", $out);
 | 
						|
            $tt[] = $out_str;
 | 
						|
        }
 | 
						|
 | 
						|
        return join("", $tt);
 | 
						|
    }
 | 
						|
 | 
						|
    public static function genTerm(array $vars, int $ftn, int $tn, int $mind, int $maxd, bool $top = true, int $opindex = 1): string
 | 
						|
    {
 | 
						|
        $term = "";
 | 
						|
        $m = max($ftn, random_int(1, $tn));
 | 
						|
        if ((($maxd === 0) || ($m === 1)) && ($ftn === 0)) {
 | 
						|
            $neg = random_int(0, 1) === 1;
 | 
						|
            $var = $vars[array_rand($vars, 1)];
 | 
						|
            $term = ($neg ? "~" : "") . $var;
 | 
						|
        } else {
 | 
						|
            $depth = random_int(0, max(0, $maxd - 1));
 | 
						|
 | 
						|
            $verilog_ops = [" & ", " | "];
 | 
						|
            $verilog_op = $verilog_ops[$opindex];
 | 
						|
            $term = !$top ? "(" : "";
 | 
						|
 | 
						|
            $nextopindex = ($opindex === 0) ? 1 : 0;
 | 
						|
 | 
						|
            for ($i = 0; $i < $m; $i++) {
 | 
						|
                $subterm = self::genTerm($vars, (($mind - 1) > 0) ? $ftn : 0, $tn, $mind - 1, $depth, false, $nextopindex);
 | 
						|
                $term .= $subterm;
 | 
						|
                if ($i < $m - 1) {
 | 
						|
                    $term .= $verilog_op;
 | 
						|
                }
 | 
						|
            }
 | 
						|
            $term .= !$top ? ")" : "";
 | 
						|
        }
 | 
						|
        return $term;
 | 
						|
    }
 | 
						|
 | 
						|
    public static function genRandom(array $input_vars, int $min_depth = 2, int $max_depth = 3): LogicFunction
 | 
						|
    {
 | 
						|
        $term = self::genTerm($input_vars, count($input_vars), count($input_vars), $min_depth, $max_depth);
 | 
						|
        return new LogicFunction($term, $input_vars);
 | 
						|
    }
 | 
						|
 | 
						|
    public static function genRandomDNF($input_vars): LogicFunction
 | 
						|
    {
 | 
						|
        $N = count($input_vars);
 | 
						|
        $states = pow(2, $N);
 | 
						|
 | 
						|
        $verilog_term = "";
 | 
						|
        for ($i = 0; $i < $states; $i++) {
 | 
						|
            $inside = "";
 | 
						|
            $omit = random_int(0, 1); // omit the term or not?
 | 
						|
            if (!$omit) {
 | 
						|
                for ($j = 0; $j < $N; $j++) {
 | 
						|
                    $neg = !($i & (1 << $j)); // is it an inverted variable?
 | 
						|
                    $term = $input_vars[$j];
 | 
						|
                    if ($neg) {
 | 
						|
                        $inside .= "~" . $term;
 | 
						|
                    } else {
 | 
						|
                        $inside .= $term;
 | 
						|
                    }
 | 
						|
                    if ($j < ($N - 1)) {
 | 
						|
                        $inside .= " & ";
 | 
						|
                    }
 | 
						|
                }
 | 
						|
            }
 | 
						|
 | 
						|
            if ($inside !== "") {
 | 
						|
                $verilog_term .= "(";
 | 
						|
                $verilog_term .= $inside;
 | 
						|
                $verilog_term .= ")";
 | 
						|
                if (($i < ($states - 1)) && !$omit) {
 | 
						|
                    $verilog_term .= " | ";
 | 
						|
                }
 | 
						|
            }
 | 
						|
        }
 | 
						|
 | 
						|
        $verilog_term = rtrim($verilog_term, "| ");
 | 
						|
        return new LogicFunction($verilog_term, $input_vars);
 | 
						|
    }
 | 
						|
 | 
						|
    public function setExpression(string $expression): void
 | 
						|
    {
 | 
						|
        $this->expression = self::convertToVerilogBitwiseForm($expression);
 | 
						|
    }
 | 
						|
 | 
						|
    public function getExpression(string $fmt = "verilog_bitwise"): string
 | 
						|
    {
 | 
						|
        switch (strtolower($fmt)) {
 | 
						|
            case "verilog_logic":
 | 
						|
                return str_replace(["&", "|", "~"], ["&&", "||", "!"], $this->expression);
 | 
						|
            case "tex":
 | 
						|
            {
 | 
						|
                $tex_form = str_replace([" | ", " & ", "(", ")"], [" + ", " \\cdot ", "\\left(", "\\right)"], $this->expression);
 | 
						|
                return preg_replace("/~([a-zA-Z0-9_])/", '\\overline{$1}', $tex_form);
 | 
						|
            }
 | 
						|
            default:
 | 
						|
            case "verilog_bitwise":
 | 
						|
                return $this->expression;
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    public function getInputVars(): array
 | 
						|
    {
 | 
						|
        return $this->input_vars;
 | 
						|
    }
 | 
						|
 | 
						|
    public function setInputVars(array $input_vars): void
 | 
						|
    {
 | 
						|
        $this->input_vars = $input_vars;
 | 
						|
    }
 | 
						|
 | 
						|
    public function getNStates(): int
 | 
						|
    {
 | 
						|
        return pow(2, count($this->input_vars));
 | 
						|
    }
 | 
						|
 | 
						|
    public function isValid(): bool
 | 
						|
    {
 | 
						|
        try {
 | 
						|
            self::$EXP_LANG->lint(self::adaptToEL($this->expression), $this->input_vars);
 | 
						|
        } catch (Exception $e) {
 | 
						|
            return false;
 | 
						|
        }
 | 
						|
        return true;
 | 
						|
    }
 | 
						|
 | 
						|
    public function toArray(): array
 | 
						|
    {
 | 
						|
        return ["expression" => $this->expression, "input_vars" => $this->input_vars];
 | 
						|
    }
 | 
						|
 | 
						|
    public function jsonSerialize()
 | 
						|
    {
 | 
						|
        return $this->toArray();
 | 
						|
    }
 | 
						|
 | 
						|
    public static function fromArray(array $a): LogicFunction
 | 
						|
    {
 | 
						|
        return new LogicFunction($a["expression"] ?? "", $a["input_vars"] ?? []);
 | 
						|
    }
 | 
						|
 | 
						|
    // general minterm regex: ([/!~]*[a-zA-Z_][a-zA-Z0-9_]*[&]{1,2})*([/!~]*[a-zA-Z_][a-zA-Z0-9_]*)
 | 
						|
    // specific regex: ([\/!~]*(<input vars>)[&]{1,2})*([\/!~]*(<input vars>))
 | 
						|
    public static function isCorrectDNF(array $input_vars, string $exp): bool
 | 
						|
    {
 | 
						|
        $exp = trim($exp); // trim spaces
 | 
						|
        if ($exp === "0") {
 | 
						|
            return true;
 | 
						|
        }
 | 
						|
        $minterms = explode("|", $exp); // break up the expression into minterms
 | 
						|
        $minterms = array_map(fn($mt) => trim($mt, " ()\t"), $minterms); // strip the parentheses off the minterms
 | 
						|
        $minterms = array_map(fn($mt) => str_replace(" ", "", $mt), $minterms); // remove spaces
 | 
						|
        $ivars = implode("|", $input_vars); // create | separated list of input vars to be used with the regular expression
 | 
						|
        $regex = "/([\/!~]*(${ivars})[&]{1,2})*([\/!~]*(${ivars}))/"; // specific regular expression
 | 
						|
        foreach ($minterms as $minterm) {
 | 
						|
            if (preg_match($regex, $minterm) !== 1) { // generally try to match the minterm
 | 
						|
                return false;
 | 
						|
            }
 | 
						|
            preg_match_all("/[\/!~]*(${ivars})[&]*/", $minterm, $matches); // fetch variables
 | 
						|
            $vars = $matches[1] ?? [];
 | 
						|
            sort($vars); // sort detected variables
 | 
						|
            sort($input_vars); // sort input variables
 | 
						|
            if ($vars !== $input_vars) { // ensure each variable occurs just once
 | 
						|
                return false;
 | 
						|
            }
 | 
						|
        }
 | 
						|
        return true;
 | 
						|
    }
 | 
						|
 | 
						|
    public static function initStatic(): void
 | 
						|
    {
 | 
						|
        self::$EXP_LANG = new ExpressionLanguage();
 | 
						|
    }
 | 
						|
 | 
						|
    public function toDNF(): string
 | 
						|
    {
 | 
						|
        $N = count($this->input_vars);
 | 
						|
        $M = pow(2, count($this->input_vars));
 | 
						|
        $tt = $this->getTruthTable();
 | 
						|
 | 
						|
        $minterms = [];
 | 
						|
        for ($i = 0; $i < $M; $i++) {
 | 
						|
            $r = $tt[$i];
 | 
						|
            if ($r == "1") {
 | 
						|
                $term = "(";
 | 
						|
                for ($j = 0; $j < $N; $j++) {
 | 
						|
                    $inv = (($i >> ($N - $j - 1)) & 1) ? "" : "~";
 | 
						|
                    $term .= $inv . $this->input_vars[$j];
 | 
						|
                    if ($j < ($N - 1)) {
 | 
						|
                        $term .= " & ";
 | 
						|
                    }
 | 
						|
                }
 | 
						|
                $term .= ")";
 | 
						|
                $minterms[] = $term;
 | 
						|
            }
 | 
						|
        }
 | 
						|
 | 
						|
        $dnf = join(" | ", $minterms);
 | 
						|
        if ($dnf === "") {
 | 
						|
            $dnf = "0";
 | 
						|
        }
 | 
						|
        return $dnf;
 | 
						|
    }
 | 
						|
 | 
						|
    public function drawNetwork(string $outvar = "f"): string
 | 
						|
    {
 | 
						|
        $expr = str_replace(["^"], [" xor "], $this->getExpression());
 | 
						|
        return PythonUtils::execPy("draw_logic_network.py", [$expr, $outvar]);
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
LogicFunction::initStatic(); |