255 lines
8.5 KiB
PHP
255 lines
8.5 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 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 = $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);
|
|
// printf("%d\n", $out);
|
|
$tt[] = $out;
|
|
}
|
|
|
|
return join("", array_map(fn($r) => ($r === True) ? 1 : 0, $tt));
|
|
}
|
|
|
|
public static function genRandom(array $input_vars, int $min_depth = 2, int $max_depth = 3): LogicFunction
|
|
{
|
|
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 = 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;
|
|
}
|
|
|
|
$term = genTerm($input_vars, count($input_vars), count($input_vars), $min_depth, $max_depth);
|
|
return new LogicFunction($term, $input_vars);
|
|
}
|
|
|
|
public static function genRandomDF($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 variable 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($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
|
|
$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;
|
|
}
|
|
}
|
|
return join(" | ", $minterms);
|
|
}
|
|
|
|
public function drawNetwork(string $fn, string $outvar = "f"): void {
|
|
PythonUtils::execPy("draw_logic_network.py", [ $this->getExpression(), $outvar, $fn ]);
|
|
}
|
|
}
|
|
|
|
LogicFunction::initStatic(); |