(************************************************************************)
(* *)
(* Optimize- *)
(* *)
(* Randall L. Hyde *)
(* 12/20/95 *)
(* For use with "The Art of Assembly Language Programming." *)
(* Copyright 1995, All Rights Reserved. *)
(* *)
(* This program lets the user enter a logic equation. The program will *)
(* convert the logic equation to a truth table and its canonical sum of *)
(* minterms form. The program will then optimize the equation (to pro- *)
(* duce a minimal number of terms) using the carnot map method. *)
(* *)
(************************************************************************)
unit Optimize;
interface
uses
SysUtils, WinTypes, WinProcs, Messages, Classes, Graphics, Controls,
Forms, Dialogs, StdCtrls, Aboutu, ExtCtrls;
type
TEqnOptimize = class(TForm)
PrintBtn: TButton; { Buttons appearing on the form }
AboutBtn: TButton;
ExitBtn: TButton;
OptimizeBtn: TButton;
CanonicalPanel: TGroupBox;
OptimizedPanel: TGroupBox;
PrintDialog: TPrintDialog;
LogEqn: TEdit; {Logic equation input box and label }
LogEqnLbl: TLabel;
Eqn1: TLabel; { Strings that hold the canonical form }
Eqn2: TLabel;
Eqn3: TLabel; { Strings that hold the optimized form }
Eqn4: TLabel;
dc00: TLabel; { Labels around the truth map }
dc01: TLabel;
dc11: TLabel;
dc10: TLabel;
ba00: TLabel;
ba01: TLabel;
ba11: TLabel;
ba10: TLabel;
tt00: TPanel; { Rectangles in the truth map }
tt01: TPanel;
tt02: TPanel;
tt03: TPanel;
tt10: TPanel;
tt11: TPanel;
tt12: TPanel;
tt13: TPanel;
tt20: TPanel;
tt21: TPanel;
tt22: TPanel;
tt23: TPanel;
tt30: TPanel;
tt31: TPanel;
tt32: TPanel;
tt33: TPanel;
StepBtn: TButton;
procedure PrintBtnClick(Sender: TObject);
procedure ExitBtnClick(Sender: TObject);
procedure AboutBtnClick(Sender: TObject);
procedure LogEqnChange(Sender: TObject);
procedure FormCreate(Sender: TObject);
procedure OptimizeBtnClick(Sender: TObject);
procedure StepBtnClick(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
EqnOptimize: TEqnOptimize;
implementation
{ Set constants for the optimization operation. These sets group the }
{ cells in the truth map that combine to form simpler terms. }
type TblSet = set of 0..15;
const
{ If the truth table is equal to the "Full" set, then we have }
{ the logic function "F=1". }
Full = [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15];
{ The TermSets list is a list of sets corresponding to the }
{ cells in the truth map that contain all ones for the terms }
{ that we can optimize away. }
TermSets : array[0..79] of TblSet =
(
{ Terms containing a single variable, e.g., A, A', B, }
{ B', and so on. }
[0,1,2,3,4,5,6,7],
[4,5,6,7,8,9,10,11],
[8,9,10,11,12,13,14,15],
[12,13,14,15,0,1,2,3],
[0,1,4,5,8,9,12,13],
[1,2,5,6,9,10,13,14],
[2,3,6,7,10,11,14,15],
[3,0,7,4,11,8,15,12],
{ Terms containing two variables, e.g., AB, AB', A'B, }
{ and so on. This first set of eight values corres- }
{ ponds to the groups of four vertical or horizontal }
{ values. The next group of 16 sets corresponds to the }
{ groups of four values that form a 2x2 square in the }
{ truth map. }
[0,1,2,3], [4,5,6,7],
[8,9,10,11], [12,13,14,15],
[0,4,8,12], [1,5,9,13],
[2,6,10,14], [3,7,11,15],
[0,1,4,5], [1,2,5,6],
[2,3,6,7], [3,0,7,4],
[4,5,8,9], [5,6,9,10],
[6,7,10,11], [7,4,11,8],
[8,9,12,13], [9,10,13,14],
[10,11,14,15], [11,8,15,12],
[12,13,0,1], [13,14,1,2],
[14,15,2,3], [15,12,3,0],
{ Terms containing three variables, e.g., ABC, ABC', }
{ and so on. }
[0,1], [1,2], [2,3], [3,0],
[4,5], [5,6], [6,7], [7,4],
[8,9], [9,10], [10,11], [11,8],
[12,13], [13,14], [14,15], [15,12],
[0,4], [4,8], [8,12], [12,0],
[1,5], [5,9], [9,13], [13,1],
[2,6], [6,10], [10,14], [14,2],
[3,7], [7,11], [11,15], [15,3],
{ Minterms- Those terms containing four variables. }
[0], [1], [2], [3],
[4], [5], [6], [7],
[8], [9], [10], [11],
[12], [13], [14], [15]
);
{ Here are the corresponding strings we output for the patterns }
{ above. }
TermStrs: array [0..79] of string[8] =
('D''', 'C', 'D', 'C''',
'B''', 'A', 'B', 'A''',
'D''C''', 'D''C', 'DC', 'DC''',
'B''A''', 'B''A', 'BA', 'BA''',
'D''B''', 'D''A', 'D''B', 'D''A''',
'CB''', 'CA', 'CB', 'CA''',
'DB''', 'DA', 'DB', 'DA''',
'C''B''', 'C''A', 'C''B', 'C''A''',
'D''C''B''', 'D''C''A', 'D''C''B', 'D''C''A''',
'D''CB''', 'D''CA', 'D''CB', 'D''CA''',
'DCB''', 'DCA', 'DCB', 'DCA''',
'DC''B''', 'DC''A', 'DC''B', 'DC''A''',
'B''A''D''', 'B''A''C', 'B''A''D', 'B''A''C''',
'B''AD''', 'B''AC', 'B''AD', 'B''AC''',
'BAD''', 'BAC', 'BAD', 'BAC''',
'BA''D''', 'BA''C', 'BA''D', 'BA''C''',
'D''C''B''A''','D''C''B''A', 'D''C''BA', 'D''C''BA''',
'D''CB''A''', 'D''CB''A', 'DC''BA', 'DC''BA''',
'DCB''A''', 'DCB''A', 'DCBA', 'DCBA''',
'DC''B''A''', 'DC''B''A', 'DC''BA', 'DC''BA'''
);
{ Transpose converts truth table indicies into truth map index- }
{ es. In reality, this converts binary numbers to a gray code. }
Transpose : array [0..15] of integer = (0, 1, 3, 2,
4, 5, 7, 6,
12,13,15,14,
8, 9, 11, 10);
var
{ Global, static, variables that the iterator uses to step }
{ through an optimization. }
IterIndex: integer;
FirstStep: boolean;
StepSet,
StepLeft,
LastSet :TblSet;
{ The following array provides convenient access to the truth map }
tt: array[0..1,0..1,0..1,0..1] of TPanel;
{$R *.DFM}
{ ApndStr- item contains '0' or '1' -- the character in the}
{ current truth table cell. theStr is a string }
{ of characters to append to the equation if item }
{ is equal to '1'. }
procedure ApndStr(item:char; const theStr:string;
var Eqn1, Eqn2:TLabel);
begin
{ To make everything fit on our form, we have to break }
{ the equation up into two lines. If the first line }
{ hits 66 characters, append the characters to the end }
{ of the second string. }
if (length(eqn1.Caption) < 66) then begin
{ If we are appending to the end of EQN1, we have to }
{ check to see if the string's length is zero. If }
{ not, then we need to stick ' + ' between the }
{ existing string and the string we are appending. }
{ If the string length is zero, this is the first }
{ minterm so we don't prepend the ' + '. }
if (item = '1') then
if (length(eqn1.Caption) = 0) then
eqn1.Caption := theStr
else eqn1.Caption := eqn1.Caption + ' + ' + theStr;
end
else if (item = '1') then
eqn2.Caption := eqn2.Caption + ' + ' + theStr;
end;
(* ComputeEqn- Computes the logic equation string from the current *)
(* truth table entries. *)
procedure ComputeEqn;
begin
with EqnOptimize do begin
eqn1.Caption := '';
eqn2.Caption := '';
{ Build the logic equation from all the squares }
{ in the truth table. }
ApndStr(tt00.Caption[1],'D''C''B''A''',Eqn1, Eqn2);
ApndStr(tt01.Caption[1],'D''C''B''A',Eqn1, Eqn2);
ApndStr(tt02.Caption[1], 'D''C''BA',Eqn1, Eqn2);
ApndStr(tt03.Caption[1], 'D''C''BA''',Eqn1, Eqn2);
ApndStr(tt10.Caption[1],'D''CB''A''',Eqn1, Eqn2);
ApndStr(tt11.Caption[1],'D''CB''A',Eqn1, Eqn2);
ApndStr(tt12.Caption[1], 'D''CBA',Eqn1, Eqn2);
ApndStr(tt13.Caption[1], 'D''CBA''',Eqn1, Eqn2);
ApndStr(tt20.Caption[1],'DCB''A''',Eqn1, Eqn2);
ApndStr(tt21.Caption[1],'DCB''A',Eqn1, Eqn2);
ApndStr(tt22.Caption[1], 'DCBA',Eqn1, Eqn2);
ApndStr(tt23.Caption[1], 'DCBA''',Eqn1, Eqn2);
ApndStr(tt30.Caption[1],'DC''B''A''',Eqn1, Eqn2);
ApndStr(tt31.Caption[1],'DC''B''A',Eqn1, Eqn2);
ApndStr(tt32.Caption[1], 'DC''BA',Eqn1, Eqn2);
ApndStr(tt33.Caption[1], 'DC''BA''',Eqn1, Eqn2);
{ If after all the above the string is empty, then we've got a }
{ truth table that contains all zeros. Handle that special }
{ case down here. }
if (length(eqn1.Caption) = 0) then
eqn1.Caption := '0';
Eqn1.Caption := 'F= ' + Eqn1.Caption;
end;
end;
procedure RestoreMap;
var a,b,c,d:integer;
begin
{ Restore the colors on the truth map. }
for d := 0 to 1 do
for c := 0 to 1 do
for b := 0 to 1 do
for a := 0 to 1 do
begin
tt[d,c,b,a].Color := clBtnFace;
end;
end;
{ InitIter-Initializes the iterator for the first optimization step. }
{ This code enables the step button, sets all the truth table }
{ squares to gray, and sets up the global variables for the }
{ very first optimization operation. }
procedure InitIter;
var
d,c,b,a:integer;
begin
with EqnOptimize do
begin
{ Initialize global variables. }
StepBtn.Enabled := true;
IterIndex := 0;
LastSet := [];
{ Restore the colors on the truth map. }
RestoreMap;
{ Compute the set of values in the truth map }
Eqn3.Caption := '';
Eqn4.Caption := '';
IterIndex := 0;
StepSet := [];
for d := 0 to 1 do
for c := 0 to 1 do
for b := 0 to 1 do
for a := 0 to 1 do
if (tt[d,c,b,a].Caption = '1') then
StepSet := StepSet + [ ((b*2+a) xor b) +
((d*2+c) xor d)*4
];
StepLeft := StepSet;
{ Check for two special cases: F=1 and F=0. The optimization }
{ algorithm cannot handle F=0 and this seems like the most }
{ appropriate place to handle F=1. So handle these two special }
{ cases here. }
if (StepSet = Full) then
begin
Eqn3.Caption := 'F = 1';
StepSet := [];
end
else if (StepSet = []) then Eqn3.Caption := 'F = 0';
end;
{ Prevent a call to this routine the next time the user presses }
{ the "Step" button. }
FirstStep := false;
end;
{ StepBtnClick- This event does a single optimization operation. Each }
{ time the user presses the "Step" button, this code does a single }
{ optimization operation; that is, it locates a single unprocessed }
{ group of ones in the truth map and optimizes them to their appropri- }
{ ate term. It highlights the optimized group so the user can easily }
{ following the optimization process. This program must call InitIter }
{ prior to the first call of this routine. In general, that call }
{ occurs in the event handler for the "Optimize" button. }
procedure TEqnOptimize.StepBtnClick(Sender: TObject);
var
a,b,c,d, i:integer;
begin
{ On the first call to this event handler (after the user presses }
{ the "Optimize" button), we need to initialize the iterator. The }
{ FirstStep flag determines whether this is the first call after }
{ the user presses optimize. }
if (FirstStep) then InitIter;
with EqnOptimize do begin
{ The user actually has to press the "Step" button twice for }
{ each optimization step. On the second press, the following }
{ code turns the squares processed on the previous depression }
{ to dark green. This helps visually convey the process to }
{ the user. }
if (LastSet <> []) then
begin
for i := 0 to 15 do
if (Transpose[i] in LastSet) then
tt[i shr 3, (i shr 2) and 1,
(i shr 1) and 1, i and 1].Color :=
clgreen;
LastSet := [];
end
{ The following code executes on the first press of each pair }
{ of "Step" button depressions. It checks to see if there is }
{ any work left to do and if so, it does exactly one optimiza- }
{ tion step. }
else if (StepLeft <> []) then
begin
{ IterIndex should always be less than 80, this is just }
{ a sanity check. }
while (IterIndex < 80) do
begin
{ If the current set of unprocessed squares }
{ matches one of the patterns we need to process}
{ then add the appropriate term to our logic }
{ equation. }
if ((TermSets[IterIndex] <= StepSet) and
((TermSets[IterIndex] * StepLeft) <> [])) then begin
ApndStr('1',TermStrs[IterIndex],Eqn3,Eqn4);
{ On the first step, we need to prepend }
{ the string "F = " to our logic eqn. }
{ The following if statement handles }
{ this chore. }
if (Eqn3.Caption[1] <> 'F') then
Eqn3.Caption := 'F = ' + Eqn3.Caption;
{ Remove the group we just processed }
{ from the truth map cells we've still }
{ got to process. }
StepLeft := StepLeft - TermSets[IterIndex];
{ Turn the cells we just processed to }
{ a bright, light, blue color (aqua). }
{ This lets the user see which cells }
{ correspond to the term we just ap- }
{ pended to the end of the equation. }
for i := 0 to 15 do
if (Transpose[i] in
TermSets[IterIndex]) then
tt[i shr 3, (i shr 2) and 1,
(i shr 1) and 1, i and 1].Color :=
claqua;
{ Save this group of cells so we can }
{ turn them dark green on the next call }
{ to this routine. }
LastSet := TermSets[IterIndex];
break;
end;
inc(IterIndex);
end;
end
{ After the last valid depression of the "Step" button, clear }
{ the truth map and disable the "Step" button. }
else begin
RestoreMap;
StepBtn.Enabled := false;
end;
end;
end;
{ OptEqn- This routine optimizes a boolean expression. This code is }
{ roughly equivalent to the "Step" event handler above except it gener- }
{ ates the optimized equation in a single call rather than in several }
{ distinct calls. }
procedure OptEqn;
var
a,b,c,d,
i :integer;
TTSet,
TLeft :TblSet;
begin
with EqnOptimize do begin
{ Generate the set of minterms we need to process. }
TTSet := [];
for d := 0 to 1 do
for c := 0 to 1 do
for b := 0 to 1 do
for a := 0 to 1 do
if (tt[d,c,b,a].Caption = '1') then
TTSet := TTSet + [ ((b*2+a) xor b) +
((d*2+c) xor d)*4
];
{ Special cases for F=1 and F=0 }
if (TTSet = Full) then
begin
Eqn3.Caption := 'F = 1';
Eqn4.Caption := '';
end
else if (TTSet = []) then
begin
Eqn3.Caption := 'F = 0';
Eqn4.Caption := '';
end
{ The following code handles all other equations. It steps }
{ through each of the possible patterns and if it finds a }
{ match it will add its corresponding term to the end of the }
{ optimized logic equation. }
else begin
TLeft := TTSet;
Eqn3.Caption := '';
Eqn4.Caption := '';
for i := 0 to 79 do begin
if ((TermSets [i] <= TTSet) and
((TermSets [i] * TLeft) <> [])) then begin
ApndStr('1',TermStrs[i],Eqn3,Eqn4);
TLeft := TLeft - TermSets[i];
end;
end;
end;
Eqn3.Caption := 'F = ' + Eqn3.Caption;
{ Now that the user has pressed the optimize button, enable }
{ the "Step" button so they can single step through the opti- }
{ mization operation. }
FirstStep := true;
StepBtn.Enabled := true;
end;
end;
{ The following event handles "Print" button depressions. }
procedure TEqnOptimize.PrintBtnClick(Sender: TObject);
begin
if (PrintDialog.Execute) then
EqnOptimize.Print;
end;
{ The following event handles "Exit" button depressions. }
procedure TEqnOptimize.ExitBtnClick(Sender: TObject);
begin
Halt;
end;
{ The following event handles "About" button depressions. }
procedure TEqnOptimize.AboutBtnClick(Sender: TObject);
begin
AboutBox.Show;
end;
{ Whenever the user changes the logic equation, we need to }
{ disable the step button. The following event does just that }
{ as well as making the "Optimize" button the default operation }
{ when the user presses the "Enter" key. }
procedure TEqnOptimize.LogEqnChange(Sender: TObject);
begin
OptimizeBtn.Default := true;
RestoreMap;
StepBtn.Enabled := false;
end;
{ Whenever the system starts up, the following procedure does }
{ some one-time initialization. }
procedure TEqnOptimize.FormCreate(Sender: TObject);
begin
{ Initialize the tt array so we can use to to access }
{ the Truth Map as a two-dimensional array. }
tt[0,0,0,0] := tt00;
tt[0,0,0,1] := tt01;
tt[0,0,1,1] := tt02;
tt[0,0,1,0] := tt03;
tt[0,1,0,0] := tt10;
tt[0,1,0,1] := tt11;
tt[0,1,1,1] := tt12;
tt[0,1,1,0] := tt13;
tt[1,0,0,0] := tt30;
tt[1,0,0,1] := tt31;
tt[1,0,1,1] := tt32;
tt[1,0,1,0] := tt33;
tt[1,1,0,0] := tt20;
tt[1,1,0,1] := tt21;
tt[1,1,1,1] := tt22;
tt[1,1,1,0] := tt23;
end;
{ Whenever the user presses the optimize button, the following }
{ procedure parses the input logic equation, generates a truth }
{ map for it, and then generates the canonical and optimized }
{ equations. }
procedure TEqnOptimize.OptimizeBtnClick(Sender: TObject);
var
CurChar:integer;
Equation:string;
{ Parse- Parses the "Equation" string and evaluates it. }
{ Returns the equation's value if legal expression, returns }
{ -1 if the equation is syntactically incorrect. }
{ }
{ Grammar: }
{ S -> X + S | X }
{ X -> YX | Y }
{ Y -> Y' | Z }
{ Z -> a | b | c | d | ( S ) }
function parse(D, C, B, A:integer):integer;
function X(D,C,B,A:integer):integer;
function Y(D,C,B,A:integer):integer;
function Z(D,C,B,A:integer):integer;
begin
case (Equation[CurChar]) of
'(': begin
CurChar := CurChar + 1;
Result := parse(D,C,B,A);
if (Equation[CurChar] <> ')') then
Result := -1
else CurChar := CurChar + 1;
end;
'a': begin
CurChar := CurChar + 1;
Result := A;
end;
'b': begin
CurChar := CurChar + 1;
Result := B;
end;
'c': begin
CurChar := CurChar + 1;
Result := C;
end;
'd': begin
CurChar := CurChar + 1;
Result := D;
end;
'0': begin
CurChar := CurChar + 1;
Result := 0;
end;
'1': begin
CurChar := CurChar + 1;
Result := 1;
end;
else Result := -1;
end;
end;
begin {Y}
{ Note: This particular operation is left recursive }
{ and would require considerable grammar transform- }
{ ation to repair. However, a simple trick is to }
{ note that the result would have tail recursion }
{ which we can solve iteratively rather than recur- }
{ sively. Hence the while loop in the following }
{ code. }
Result := Z(D,C,B,A);
while (Result <> -1) and (Equation[CurChar] = '''') do
begin
Result := Result xor 1;
CurChar := CurChar + 1;
end;
end;
begin {X}
Result := Y(D,C,B,A);
if (Result <> -1) and
(Equation[CurChar] in ['a'..'d', '0', '1', '(']) then
Result := Result AND X(D,C,B,A);
end;
begin
Result := X(D,C,B,A);
if (Result <> -1) and (Equation[CurChar] = '+') then begin
CurChar := CurChar + 1;
Result := Result OR parse(D, C, B, A);
end;
end;
var
a, b, c, d:integer;
dest:integer;
i:integer;
begin {ComputeBtnClick}
Equation := LowerCase(LogEqn.Text) + chr(0);
{ Remove any spaces present in the string }
dest := 1;
for i := 1 to length(Equation) do
if (Equation[i] <> ' ') then begin
Equation[dest] := Equation[i];
dest := dest + 1;
end;
{ Okay, see if this string is syntactically legal. }
CurChar := 1; {Start at position 1 in string }
if (Parse(0,0,0,0) <> -1) then
if (Equation[CurChar] = chr(0)) then begin
{ Compute the values for each of the squares in }
{ the truth table. }
for d := 0 to 1 do
for c := 0 to 1 do
for b := 0 to 1 do
for a := 0 to 1 do begin
CurChar := 1;
if (parse(d,c,b,a) = 0) then
tt[d,c,b,a].Caption := '0'
else tt[d,c,b,a].Caption := '1';
end;
ComputeEqn;
OptEqn;
LogEqn.Color := clWindow;
RestoreMap;
end
else LogEqn.Color := clRed
else LogEqn.Color := clRed;
end;
end.