#!/usr/bin/perl -I/home/phil/perl/cpan/SvgSimple/lib/
#-------------------------------------------------------------------------------
# Design a silicon chip by combining gates and sub chips.
# Philip R Brenan at appaapps dot com, Appa Apps Ltd Inc., 2023
#-------------------------------------------------------------------------------
# Update sizes from sub chips
use v5.34;
package Silicon::Chip;
our $VERSION = 20231111; # Version
use warnings FATAL => qw(all);
use strict;
use Carp;
use Data::Dump qw(dump);
use Data::Table::Text qw(:all);
makeDieConfess;
my sub maxSimulationSteps {100} # Maximum simulation steps
my sub gateNotIO {0} # Not an input or output gate
my sub gateInternalInput {1} # Input gate on an internal chip
my sub gateInternalOutput {2} # Output gate on an internal chip
my sub gateExternalInput {3} # Input gate on the external chip
my sub gateExternalOutput {4} # Output gate on the external chip
my sub gateOuterInput {5} # Input gate on the external chip connecting to the outer world
my sub gateOuterOutput {6} # Output gate on the external chip connecting to the outer world
my $possibleTypes = q(and|continue|gt|input|lt|nand|nor|not|nxor|one|or|output|xor|zero);#Substitute: possible gate types
#D1 Construct # Construct a L<silicon> L<chip> using standard L<lgs>, components and sub chips combined via buses.
sub newChip(%) # Create a new L<chip>.
{my (%options) = @_; # Options
!@_ or !ref($_[0]) or confess "Call as a sub not as a method";
genHash(__PACKAGE__, # Chip description
name => $options{name} // $options{title} // "Unnamed chip: ".timeStamp, # Name of chip
gates => $options{gates} // {}, # Gates in chip
installs => $options{installs} // [], # Chips installed within the chip
title => $options{title}, # Title if known
gateSeq => 0, # Gate sequence number - this allows us to display the gates in the order they were defined ti simplify the understanding of drawn layouts
sizeBits => {}, # Sizes of buses
sizeWords => {}, # Sizes of buses
);
}
my sub newGate($$$$) # Make a L<lg>.
{my ($chip, $type, $output, $inputs) = @_; # Chip, gate type, output name, input names to output from another gate
my $g = genHash("Silicon::Chip::Gate", # Gate
type => $type, # Gate type
output => $output, # Output name which is used as the name of the gate as well
inputs => $inputs, # Input names to driving outputs
io => gateNotIO, # Whether an input/output gate or not
seq => ++$chip->gateSeq, # Sequence number for this gate
);
}
my sub validateName($$%) # Confirm that a component name looks like a variable name and has not already been used
{my ($chip, $output, %options) = @_; # Chip, name, options
my $gates = $chip->gates; # Gates implementing the chip
$output =~ m(\A[a-z][a-z0-9_.:]*\Z)i or confess <<"END";
Invalid gate name: '$output'
END
$$gates{$output} and confess <<"END";
Gate: '$output' has already been specified
END
1
}
sub gate($$$;$$) # A L<lg> chosen from B<possibleTypes>. The gate type can be used as a method name, so B<-E<gt>gate("and",> can be reduced to B<-E<gt>and(>.
{my ($chip, $type, $output, $input1, $input2) = @_; # Chip, gate type, output name, input from another gate, input from another gate
@_ >= 3 or confess "Three or more parameters";
my $gates = $chip->gates; # Gates implementing the chip
my $inputs; # Input hash mapping used to accept outputs from other gates as inputs for this gate
validateName $chip, $output; # Validate the name of the gate
if ($type =~ m(\A(input)\Z)i) # Input gates input to themselves unless they have been connected to an output gate during sub chip expansion
{@_> 3 and confess <<"END";
No input hash allowed for input gate: '$output'
END
$inputs = {$output=>$output}; # Convert convenient scalar name to hash for consistency with gates in general
}
elsif ($type =~ m(\A(one|zero)\Z)i) # Input gates input to themselves unless they have been connected to an output gate during sub chip expansion
{@_> 3 and confess <<"END";
No input hash allowed for '$type' gate: '$output'
END
$inputs = {}; # Convert convenient scalar name to hash for consistency with gates in general
}
elsif ($type =~ m(\A(output)\Z)i) # Output has one optional scalar value naming its input if known at this point
{if (defined($input1))
{ref($input1) and confess <<"END";
Scalar input name required for input on output gate: '$output'
END
$inputs = {$output=>$input1}; # Convert convenient scalar name to hash for consistency with gates in general
}
}
elsif ($type =~ m(\A(continue|not)\Z)i) # These gates have one input expressed as a name rather than a hash
{!defined($input1) and confess "Input name required for gate: '$output'\n";
$type =~ m(\Anot\Z)i and ref($input1) =~ m(hash)i and confess <<"END";
Scalar input name required for: '$output'
END
$inputs = {$output=>$input1}; # Convert convenient scalar name to hash for consistency with gates in general
}
elsif ($type =~ m(\A(nxor|xor|gt|ngt|lt|nlt)\Z)i) # These gates must have exactly two inputs expressed as a hash mapping input pin name to connection to a named gate. These operations are associative.
{!defined($input1) and confess <<"END" =~ s/\n/ /gsr;
Input one required for gate: '$output'
END
!defined($input2) and confess <<"END" =~ s/\n/ /gsr;
Input two required for gate: '$output'
END
ref($input1) and confess <<"END" =~ s/\n/ /gsr;
Input one must be the name of the connecting gate.
END
ref($input2) and confess <<"END" =~ s/\n/ /gsr;
Input two must be the name of the connecting gate.
END
$inputs = {1=>$input1, 2=>$input2}; # Construct the inputs hash expected in general for these two input gates
}
elsif ($type =~ m(\A(and|nand|nor|or)\Z)i) # These gates must have two or more inputs expressed as a hash mapping input pin name to connection to a named gate. These operations are associative.
{!defined($input1) and confess <<"END" =~ s/\n/ /gsr;
Input hash required for gate: '$output'
END
if (ref($input1) =~ m(hash)i)
{$inputs = $input1;
}
elsif (ref($input1) =~ m(array)i)
{$inputs = {map {$_=>$$input1[$_]} keys @$input1};
}
else
{confess <<"END" =~ s/\n/ /gsr;
Inputs must be either a hash of input gate
names to output gate names or an array of input gate name for gate: '$output'
END
}
}
else # Unknown gate type
{confess <<"END" =~ s/\n/ /gsr;
Unknown gate type: '$type' for gate: '$output',
possible types are: '$possibleTypes
END
}
$chip->gates->{$output} = newGate($chip, $type, $output, $inputs); # Construct gate, save it and return it
}
our $AUTOLOAD; # The method to be autoloaded appears here. This allows us to have gate names like 'or' and 'and' without overwriting the existing Perl operators with these names.
sub AUTOLOAD($@) #P Autoload by L<lg> name to provide a more readable way to specify the L<lgs> on a L<chip>.
{my ($chip, @options) = @_; # Chip, options
my $type = $AUTOLOAD =~ s(\A.*::) ()r;
if ($type !~ m(\A($possibleTypes|DESTROY)\Z)) # Select autoload requests we can process as gate names
{confess <<"END" =~ s/\n/ /gsr;
Unknown method: '$type'
END
}
&gate($chip, $type, @options) if $type =~ m(\A($possibleTypes)\Z);
}
my sub cloneGate($$) # Clone a L<lg> on a L<chip>.
{my ($chip, $gate) = @_; # Chip, gate
my %i = $gate->inputs ? $gate->inputs->%* : (); # Copy inputs
newGate($chip, $gate->type, $gate->output, {%i})
}
my sub getGate($$) # Details of a named gate or confess if no such gate
{my ($chip, $name) = @_; # Chip, gate name
my $g = $chip->gates->{$name}; # Gate details
$g or confess "No such gate as $name\n";
$g
}
my sub renameGateInputs($$$) # Rename the inputs of a L<lg> on a L<chip>.
{my ($chip, $gate, $name) = @_; # Chip, gate, prefix name
for my $p(qw(inputs))
{my %i;
my $i = $gate->inputs;
for my $n(sort keys %$i)
{$i{$n} = sprintf "(%s %s)", $name, $$i{$n};
}
$gate->inputs = \%i;
}
$gate
}
my sub renameGate($$$) # Rename a L<lg> on a L<chip> by adding a prefix.
{my ($chip, $gate, $name) = @_; # Chip, gate, prefix name
$gate->output = sprintf "(%s %s)", $name, $gate->output;
$gate
}
sub connectInput($$$%) # Connect a previously defined input gate to the output of another previously gate on the same chip. This allows us to define a set of gates on the chip without having to know, first, all the names of the gates that will provide input to these gates.
{my ($chip, $in, $to, %options) = @_; # Chip, input gate, gate to connect input gate to, options
my $gates = $chip->gates;
my $i = $$gates{$in};
defined($i) or confess "No definition of input gate $in";
$i->type =~ m(\Ainput\Z) or confess "No definition of input gate $in";
$i->inputs = {1=>$to};
$chip
}
#D2 Buses # A bus is an array of bits or an array of arrays of bits
#D3 Bits # An array of bits that can be manipulated via one name.
my sub sizeBits($$%) # Size of a bits bus.
{my ($chip, $name, %options) = @_; # Chip, bits bus name, options
return $options{bits} if defined($options{bits});
my $s = $chip->sizeBits->{$name};
defined($s) or confess "No bit bus named ".dump($name)."\n";
$s
}
sub setSizeBits($$$%) # Set the size of a bits bus.
{my ($chip, $name, $bits, %options) = @_; # Chip, bits bus name, options
@_ >= 3 or confess "Three or more parameters";
defined($chip->sizeBits->{$name}) and confess <<"END";
A bit bus with name: $name has already been defined
END
$chip->sizeBits->{$name} = $bits;
$chip
}
sub bits($$$$%) # Create a bus set to a specified number.
{my ($chip, $name, $bits, $value, %options) = @_; # Chip, name of bus, width in bits of bus, value of bus, options
@_ >= 4 or confess "Four or more parameters";
my @b = reverse split //, sprintf "%0${bits}b", $value; # Bits needed
for my $b(1..@b) # Generate constant
{my $v = $b[$b-1]; # Bit value
$chip->one (n($name, $b)) if $v; # Set 1
$chip->zero(n($name, $b)) unless $v; # Set 0
}
setSizeBits($chip, $name, $bits); # Record bus width
}
sub inputBits($$$%) # Create an B<input> bus made of bits.
{my ($chip, $name, $bits, %options) = @_; # Chip, name of bus, width in bits of bus, options
@_ >= 3 or confess "Three or more parameters";
setSizeBits($chip, $name, $bits); # Record bus width
map {$chip->input(n($name, $_))} 1..$bits; # Bus of input gates
}
sub outputBits($$$%) # Create an B<output> bus made of bits.
{my ($chip, $name, $input, %options) = @_; # Chip, name of bus, name of inputs, options
@_ >= 3 or confess "Three or more parameters";
my $bits = sizeBits($chip, $input, %options);
map {$chip->output(n($name, $_), n($input, $_))} 1..$bits; # Bus of output gates
setSizeBits($chip, $name, $bits); # Record bus width
}
sub notBits($$$%) # Create a B<not> bus made of bits.
{my ($chip, $name, $input, %options) = @_; # Chip, name of bus, name of inputs, options
@_ >= 3 or confess "Three or more parameters";
my $bits = sizeBits($chip, $input, %options);
map {$chip->not(n($name, $_), n($input, $_))} 1..$bits; # Bus of not gates
setSizeBits($chip, $name, $bits); # Record bus width
}
sub andBits($$$%) # B<and> a bus made of bits.
{my ($chip, $name, $input, %options) = @_; # Chip, name of bus, name of inputs, options
@_ >= 3 or confess "Three or more parameters";
my $bits = sizeBits($chip, $input, %options);
$chip->and($name, {map {($_=>n($input, $_))} 1..$bits}); # Combine inputs in one B<and> gate
setSizeBits($chip, $name, $bits); # Record bus width
}
sub nandBits($$$%) # B<nand> a bus made of bits.
{my ($chip, $name, $input, %options) = @_; # Chip, name of bus, name of inputs, options
@_ >= 3 or confess "Three or more parameters";
my $bits = sizeBits($chip, $input, %options);
$chip->nand($name, {map {($_=>n($input, $_))} 1..$bits}); # Combine inputs in one B<nand> gate
setSizeBits($chip, $name, $bits); # Record bus width
}
sub orBits($$$%) # B<or> a bus made of bits.
{my ($chip, $name, $input, %options) = @_; # Chip, name of bus, options
@_ >= 3 or confess "Three or more parameters";
my $bits = sizeBits($chip, $input, %options);
$chip->or($name, {map {($_=>n($input, $_))} 1..$bits}); # Combine inputs in one B<or> gate
setSizeBits($chip, $name, $bits); # Record bus width
}
sub norBits($$$%) # B<nor> a bus made of bits.
{my ($chip, $name, $input, %options) = @_; # Chip, name of bus, options
@_ >= 3 or confess "Three or more parameters";
my $bits = sizeBits($chip, $input, %options);
$chip->nor($name, {map {($_=>n($input, $_))} 1..$bits}); # Combine inputs in one B<nor> gate
setSizeBits($chip, $name, $bits); # Record bus width
}
#D3 Words # An array of arrays of bits that can be manipulated via one name.
my sub sizeWords($$%) # Size of a words bus.
{my ($chip, $name, %options) = @_; # Chip, word bus name, options
my $s = $chip->sizeWords->{$name};
my $w = $options{bits} // $$s[0];
my $b = $options{words} // $$s[1];
defined($w) or confess "No words width specified or defaulted for $name";
defined($b) or confess "No bits width specified or defaulted for $name";
($w, $b)
}
sub setSizeWords($$$$%) # Set the size of a bits bus.
{my ($chip, $name, $words, $bits, %options) = @_; # Chip, bits bus name, words, bits per word, options
@_ >= 4 or confess "Four or more parameters";
defined($chip->sizeWords->{$name}) and confess <<"END";
A word bus with name: $name has already been defined
END
$chip->sizeWords->{$name} = [$words, $bits]; # Word bus size
setSizeBits($chip, n($name, $_), $bits) for 1..$words; # Size of bit bus for each word in the word bus
$chip
}
sub words($$$@) # Create a word bus set to specified numbers.
{my ($chip, $name, $bits, @values) = @_; # Chip, name of bus, width in bits of each word, values of words
@_ >= 3 or confess "Three or more parameters";
for my $w(1..@values) # Each value to put on the bus
{my $value = $values[$w-1]; # Each value to put on the bus
my @b = reverse split //, sprintf "%0${bits}b", $value; # Bits needed
for my $b(1..@b) # Generate constant
{my $v = $b[$b-1]; # Bit value
$chip->one (nn($name, $w, $b)) if $v; # Set 1
$chip->zero(nn($name, $w, $b)) unless $v; # Set 0
}
}
setSizeWords($chip, $name, scalar(@values), $bits); # Record bus width
}
sub inputWords($$$$%) # Create an B<input> bus made of words.
{my ($chip, $name, $words, $bits, %options) = @_; # Chip, name of bus, width in words of bus, width in bits of each word on bus, options
@_ >= 4 or confess "Four or more parameters";
for my $w(1..$words) # Each word on the bus
{map {$chip->input(nn($name, $w, $_))} 1..$bits; # Bus of input gates
}
setSizeWords($chip, $name, $words, $bits); # Record bus size
}
sub outputWords($$$%) # Create an B<output> bus made of words.
{my ($chip, $name, $input, %options) = @_; # Chip, name of bus, name of inputs, options
@_ >= 3 or confess "Three or more parameters";
my ($words, $bits) = sizeWords($chip, $input);
for my $w(1..$words) # Each word on the bus
{map {$chip->output(nn($name, $w, $_), nn($input, $w, $_))} 1..$bits; # Bus of output gates
}
setSizeWords($chip, $name, $words, $bits); # Record bus size
}
sub notWords($$$%) # Create a B<not> bus made of words.
{my ($chip, $name, $input, %options) = @_; # Chip, name of bus, name of inputs, options
@_ >= 3 or confess "Three or more parameters";
my ($words, $bits) = sizeWords($chip, $input, %options);
for my $w(1..$words) # Each word on the bus
{map {$chip->not(nn($name, $w, $_), nn($input, $w, $_))} 1..$bits; # Bus of not gates
}
setSizeWords($chip, $name, $words, $bits); # Record bus size
}
sub andWords($$$%) # B<and> a bus made of words to produce a single word.
{my ($chip, $name, $input, %options) = @_; # Chip, name of bus, name of inputs, options
@_ >= 3 or confess "Three or more parameters";
my ($words, $bits) = sizeWords($chip, $input, %options);
for my $w(1..$words) # Each word on the bus
{$chip->andBits(n($name, $w), n($input, $w)); # Combine inputs using B<and> gates
}
setSizeBits($chip, $name, $words); # Record bus size
}
sub andWordsX($$$%) # B<and> a bus made of words by and-ing the corresponding bits in each word to make a single word.
{my ($chip, $name, $input, %options) = @_; # Chip, name of bus, name of inputs, options
@_ >= 3 or confess "Three or more parameters";
my ($words, $bits) = sizeWords($chip, $input, %options);
for my $b(1..$bits) # Each word on the bus
{$chip->and(n($name, $b), {map {($_=>nn($input, $_, $b))} 1..$words}); # Combine inputs using B<and> gates
}
setSizeBits($chip, $name, $bits); # Record bus size
}
sub orWords($$$%) # B<or> a bus made of words to produce a single word.
{my ($chip, $name, $input, %options) = @_; # Chip, name of bus, name of inputs, options
@_ >= 3 or confess "Three or more parameters";
my ($words, $bits) = sizeWords($chip, $input, %options);
for my $w(1..$words) # Each word on the bus
{$chip->orBits(n($name, $w), n($input, $w)); # Combine inputs using B<or> gates
}
setSizeBits($chip, $name, $words); # Record bus size
}
sub orWordsX($$$%) # B<or> a bus made of words by or-ing the corresponding bits in each word to make a single word.
{my ($chip, $name, $input, %options) = @_; # Chip, name of bus, name of inputs, options
@_ >= 3 or confess "Three or more parameters";
my ($words, $bits) = sizeWords($chip, $input, %options);
for my $b(1..$bits) # Each word on the bus
{$chip->or(n($name, $b), {map {($_=>nn($input, $_, $b))} 1..$words}); # Combine inputs using B<or> gates
}
setSizeBits($chip, $name, $bits); # Record bus size
}
#D2 Install # Install a chip within a chip as a sub chip.
sub install($$$$%) # Install a L<chip> within another L<chip> specifying the connections between the inner and outer L<chip>. The same L<chip> can be installed multiple times as each L<chip> description is read only.
{my ($chip, $subChip, $inputs, $outputs, %options) = @_; # Outer chip, inner chip, inputs of inner chip to to outputs of outer chip, outputs of inner chip to inputs of outer chip, options
@_ >= 4 or confess "Four or more parameters";
my $c = genHash("Silicon::Chip::Install", # Installation of a chip within a chip
chip => $subChip, # Chip being installed
inputs => $inputs, # Outputs of outer chip to inputs of inner chip
outputs => $outputs, # Outputs of inner chip to inputs of outer chip
);
push $chip->installs->@*, $c; # Install chip
$c
}
my sub getGates($%) # Get the L<lgs> of a L<chip> and all it installed sub chips.
{my ($chip, %options) = @_; # Chip, options
my %outerGates;
for my $g(sort {$a->seq <=> $b->seq} values $chip->gates->%*) # Copy gates from outer chip
{my $G = $outerGates{$g->output} = cloneGate($chip, $g);
if ($G->type =~ m(\Ainput\Z)i) {$G->io = gateExternalInput} # Input gate on outer chip
elsif ($G->type =~ m(\Aoutput\Z)i) {$G->io = gateExternalOutput} # Output gate on outer chip
}
my @installs = $chip->installs->@*; # Each sub chip used in this chip
for my $install(keys @installs) # Each sub chip
{my $s = $installs[$install]; # Sub chip installed in this chip
my $n = $s->chip->name; # Name of sub chip
my $innerGates = __SUB__->($s->chip); # Gates in sub chip
for my $G(sort {$$innerGates{$a}->seq <=> $$innerGates{$b}->seq}
keys %$innerGates) # Each gate in sub chip on definition order
{my $g = $$innerGates{$G}; # Gate in sub chip
my $o = $g->output; # Name of gate
my $copy = cloneGate $chip, $g; # Clone gate from chip description
my $newGateName = sprintf "$n %d", $install+1; # Rename gates to prevent name collisions from the expansions of the definitions of the inner chips
if ($copy->type =~ m(\Ainput\Z)i) # Input gate on inner chip - connect to corresponding output gate on containing chip
{my $in = $copy->output; # Name of input gate on inner chip
my $o = $s->inputs->{$in};
$o or confess <<"END";
No connection specified to inner input gate: '$in' on sub chip: '$n'
END
my $O = $outerGates{$o};
$O or confess <<"END" =~ s(\n) ( )gsr;
No outer output gate '$o' to connect to inner input gate: '$in'
on sub chip: '$n'
END
my $ot = $O->type;
my $on = $O->output;
$ot =~ m(\Aoutput\Z)i or confess <<"END" =~ s(\n) ( )gsr;
Output gate required for connection to: '$in' on sub chip $n,
not: '$ot' gate: '$on'
END
$copy->inputs = {1 => $o}; # Connect inner input gate to outer output gate
renameGate $chip, $copy, $newGateName; # Add chip name to gate to disambiguate it from any other gates
$copy->io = gateInternalInput; # Mark this as an internal input gate
}
elsif ($copy->type =~ m(\Aoutput\Z)i) # Output gate on inner chip - connect to corresponding input gate on containing chip
{my $on = $copy->output; # Name of output gate on outer chip
my $i = $s->outputs->{$on};
$i or confess <<"END";
No connection specified to inner output gate: '$on' on sub chip: '$n'
END
my $I = $outerGates{$i};
$I or confess <<"END";
No outer input gate: '$i' to connect to inner output gate: $on on sub chip: '$n'
END
my $it = $I->type;
my $in = $I->output;
$it =~ m(\Ainput\Z)i or confess <<"END" =~ s(\n) ( )gsr;
Input gate required for connection to '$in' on sub chip '$n',
not gate '$in' of type '$it'
END
renameGateInputs $chip, $copy, $newGateName;
renameGate $chip, $copy, $newGateName;
$I->inputs = {11 => $copy->output}; # Connect inner output gate to outer input gate
$copy->io = gateInternalOutput; # Mark this as an internal output gate
}
else # Rename all other gate inputs
{renameGateInputs $chip, $copy, $newGateName;
renameGate $chip, $copy, $newGateName;
}
$outerGates{$copy->output} = $copy; # Install gate with new name now it has been connected up
}
}
\%outerGates # Return all the gates in the chip extended by its sub chips
}
my sub checkIO($%) # Check that each input L<lg> is connected to one output L<lg>.
{my ($chip, %options) = @_; # Chip, options
my $gates = $chip->gates; # Gates on chip
my %o;
for my $G(sort keys %$gates) # Find all inputs and outputs
{my $g = $$gates{$G}; # Address gate
my $t = $g->type; # Type of gate
my %i = $g->inputs->%*; # Inputs for gate
for my $i(sort keys %i) # Each input
{my $o = $i{$i}; # Output driving input
defined($o) or confess <<"END"; # No driving output
No output driving input pin '$i' on '$t' gate '$G'
END
my $O = $$gates{$o};
defined($O) or confess <<"END"; # No driving output
No output driving input '$o' on '$t' gate '$G'
END
if ($g->io != gateOuterInput) # The gate must inputs driven by the outputs of other gates
{$o{$o}++; # Show that this output has been used
my $T = $O->type;
if ($g->type =~ m(\Ainput\Z)i)
{$O->type =~ m(\Aoutput\Z)i or confess <<"END" =~ s(\n) ( )gsr;
Input gate: '$G' must connect to an output gate on pin: '$i'
not to '$T' gate: '$o'
END
}
elsif (!$g->io) # Not an io gate so it cannot have an input from an output gate
{$O->type =~ m(\Aoutput\Z) and confess <<"END";
Cannot drive a '$t' gate: '$G' using output gate: '$o'
END
}
}
}
}
for my $G(sort keys %$gates) # Check all inputs and outputs are being used
{my $g = $$gates{$G}; # Address gate
next if $g->type =~ m(\Aoutput\Z)i;
$o{$G} or confess <<"END" =~ s/\n/ /gsr;
Output from gate '$G' is never used
END
}
}
my sub setOuterGates($$%) # Set outer L<lgs> on external chip that connect to the outer world.
{my ($chip, $gates, %options) = @_; # Chip, gates in chip plus all sub chips as supplied by L<getGates>.
for my $G(sort keys %$gates) # Find all inputs and outputs
{my $g = $$gates{$G}; # Address gate
next unless $g->io == gateExternalInput; # Input on external chip
my ($i) = values $g->inputs->%*;
$g->io = gateOuterInput if $g->output eq $i; # Unconnected input gates reflect back on themselves - this is a short hand way of discovering such gates
}
gate: for my $G(sort keys %$gates) # Find all inputs and outputs
{my $g = $$gates{$G}; # Address gate
next unless $g->io == gateExternalOutput; # Output on external chip
for my $H(sort keys %$gates) # Gates driven by this gate
{next if $G eq $H;
my %i = $$gates{$H}->inputs->%*; # Inputs to this gate
for my $I(sort keys %i) # Each input
{next gate if $i{$I} eq $G; # Found a gate that accepts input from this gate
}
}
$g->io = gateOuterOutput; # Does not drive any other gate
}
}
my sub removeExcessIO($$%) # Remove unneeded IO L<lgs> .
{my ($chip, $gates, %options) = @_; # Chip, gates in chip plus all sub chips as supplied by L<getGates>.
my %d; # Names of gates to delete
for(;;) # Multiple passes until no more gates can be replaced
{my $changes = 0;
gate: for my $G(sort keys %$gates) # Find all inputs and outputs
{my $g = $$gates{$G}; # Address gate
next unless $g->io; # Skip non IO gates
next if $g->io == gateOuterInput or $g->io == gateOuterOutput; # Cannot be collapsed
my ($n) = values $g->inputs->%*; # Name of the gate driving this gate
for my $H(sort keys %$gates) # Gates driven by this gate
{next if $G eq $H;
my $h = $$gates{$H}; # Address gate
my %i = $h->inputs->%*; # Inputs
for my $i(sort keys %i) # Each input
{if ($i{$i} eq $G) # Found a gate that accepts input from this gate
{my $replace = $h->inputs->{$i};
$h->inputs->{$i} = $n; # Bypass io gate
$d{$G}++; # Delete this gate
++$changes; # Count changes in this pass
}
}
}
}
last unless $changes;
}
for my $d(sort keys %d) # Gates to delete
{delete $$gates{$d};
}
}
#D1 Visualize # Visualize the L<chip> in various ways.
my sub orderGates($%) # Order the L<lgs> on a L<chip> so that input L<lg> are first, the output L<lgs> are last and the non io L<lgs> are in between. All L<lgs> are first ordered alphabetically. The non io L<lgs> are then ordered by the step number at which they last changed during simulation of the L<chip>.
{my ($chip, %options) = @_; # Chip, options
my $gates = $chip->gates; # Gates on chip
my @i; my @n; my @o;
for my $G(sort {$$gates{$a}->seq <=> $$gates{$b}->seq} keys %$gates) # Dump each gate one per line in definition order
{my $g = $$gates{$G};
push @i, $G if $g->type =~ m(\Ainput\Z)i;
push @n, $G if $g->type !~ m(\A(in|out)put\Z)i;
push @o, $G if $g->type =~ m(\Aoutput\Z)i;
}
if (my $c = $options{changed}) # Order non IO gates by last change time during simulation if possible
{@n = sort {($$c{$a}//0) <=> ($$c{$b}//0)} @n;
}
(\@i, \@n, \@o)
}
sub print($%) # Dump the L<lgs> present on a L<chip>.
{my ($chip, %options) = @_; # Chip, gates, options
my $gates = $chip->gates; # Gates on chip
my $values = $options{values}; # Values of each gate if known
my @s;
my ($i, $n, $o) = orderGates $chip, %options; # Gates by type
for my $G(@$i, @$n, @$o) # Dump each gate one per line
{my $g = $$gates{$G};
my %i = $g->inputs ? $g->inputs->%* : ();
my $p = sub # Instruction name and type
{my $v = $$values{$G}; # Value if known for this gate
my $o = $g->output;
my $t = $g->type;
return sprintf "%-32s: %3d %-32s", $o, $v, $t if defined($v); # With value
return sprintf "%-32s: %-32s", $o, $t; # Without value
}->();
if (my @i = map {$i{$_}} sort keys %i) # Add actual inputs in same line sorted in input pin name
{$p .= join " ", @i;
}
push @s, $p;
}
my $s = join "\n", @s, ''; # Representation of gates as text
owf fpe($options{print}, q(txt)), $s if $options{print}; # Write representation of gates as text to the named file
$s
}
sub Silicon::Chip::Simulation::print($%) # Print simulation results as text.
{my ($sim, %options) = @_; # Simulation, options
$sim->chip->print(%options, values=>$sim->values);
}
my sub newGatePosition(%) # Specify the position of a L<lg> on a drawing of the containing L<chip>.
{my (%options) = @_; # Options
genHash("Silicon::Chip::Gate::Position", # Gate position
gate => $options{gate} // undef, # Gate
x => $options{x} // undef, # X position of gate
y => $options{y} // undef, # Y position of gate
width => $options{width} // undef, # Width of gate
)
}
sub printSvg($%) # Dump the L<lgs> on a L<chip> as an L<svg> drawing to help visualize the structure of the L<chip>.
{my ($chip, %options) = @_; # Chip, options
my $gates = $chip->gates; # Gates on chip
my $title = $chip->title; # Title of chip
my $changed = $options{changed}; # Step at which gate last changed in simulation
my $values = $options{values}; # Values of each gate if known
my $steps = $options{steps}; # Number of steps to equilibrium
my $fs = 0.2; my $fw = 0.02; # Font sizes
my $Fs = 0.4; my $Fw = 0.04;
my $op0 = q(transparent);
my $s = Svg::Simple::new(defaults=>{stroke_width=>$fw, font_size=>$fs}); # Draw each gate via Svg
my %p; # Dimensions and drawing positions of gates
my ($iG, $nG, $oG) = orderGates $chip, %options; # Gates by type
for my $i(keys @$iG) # Index of each input gate
{my $G = $$iG[$i]; # Gate name
my $g = $$gates{$G}; # Gate
$p{$G} = newGatePosition(gate=>$g, x=>0, y=>$i, width=>1); # Position input gate
}
my $W = 0; # Number of inputs to all the non IO gates
for my $i(keys @$nG) # Index of each non IO gate
{my $G = $$nG[$i]; # Gate name
my $g = $$gates{$G}; # Gate
my $w = keys($g->inputs->%*) || 1; # Width of gate has to be wide enough to accommodate all inputs
$p{$G} = newGatePosition(gate=>$g, x=>$W+1, y=>@$iG+$i, width=>$w); # Position non io gate
$W += $w; # Width of area needed for non io gates
}
for my $i(keys @$oG) # Index of each output gate
{my $G = $$oG[$i]; # Gate name
my $g = $$gates{$G}; # Gate
my %i = $g->inputs ? $g->inputs->%* : (); # Inputs to gate
my ($d) = values %i; # The one driver for this gate
next unless defined $p{$d};
my $y = $p{$d}->y;
$p{$G} = newGatePosition(gate=>$g, x=>1+$W, y=>$y, width=>1); # Position output gate
}
my $pageWidth = $W + 2; # Width of input, output and non io gates as laid out.
if (defined($title)) # Title if known
{$s->text(x=>$pageWidth, y=>0.5, fill=>"darkGreen", text_anchor=>"end",
stroke_width=>$Fw, font_size=>$Fs,
cdata=>$title);
}
if (defined($steps)) # Number of steps taken if known
{$s->text(x=>$pageWidth, y=>1.5, fill=>"darkGreen", text_anchor=>"end",
stroke_width=>$Fw, font_size=>$Fs,
cdata=>"$steps steps");
}
for my $P(sort keys %p) # Each gate with text describing it
{my $p = $p{$P};
my $x = $p->x;
my $y = $p->y;
my $w = $p->width;
my $g = $p->gate;
my $color = sub
{return "red" if $g->io == gateOuterOutput;
return "blue" if $g->io == gateOuterInput;
"green"
}->();
if ($g->io) # Circle for io pin
{$s->circle(cx=>$x+1/2, cy=>$y+1/2, r=>1/2, fill=>$op0, stroke=>$color);
}
else # Rectangle for non io gate
{$s->rect(x=>$x, y=>$y, width=>$w, height=>1, fill=>$op0, stroke=>$color);
}
if (defined(my $v = $$values{$g->output})) # Value of gate if known
{$s->text(
x => $g->io != gateOuterOutput ? $x : $x + 1,
y => $y,
fill =>"black",
stroke_width =>$Fw, font_size=>$Fs,
text_anchor => $g->io != gateOuterOutput ? "start": "end",
dominant_baseline => "hanging",
cdata => $v ? "1" : "0");
}
my sub ot($$$$) # Output svg text
{my ($dy, $fill, $pos, $text) = @_;
$s->text(x => $x+$w/2,
y => $y+$dy,
fill => $fill,
text_anchor => "middle",
dominant_baseline => $pos,
cdata => $text);
}
ot(5/12, "red", "auto", $g->type); # Type of gate
ot(7/12, "darkblue", "hanging", $g->output);
if ($g->io != gateOuterInput) # Not an input pin
{my %i = $g->inputs ? $g->inputs->%* : ();
my @i = sort values %i; # Connections to each gate
my $o = $g->output;
for my $i(keys @i) # Connections to each gate
{my $P = $p{$i[$i]}; # Source gate
defined($P) or confess <<"END";
No such gate as: '$i[$i]' on gate $o
END
my $X = $P->x; my $Y = $P->y; my $W = $P->width; my $G = $P->gate; # Position of gate
my $dx = $i + 1/2;
my $dy = $Y < $y ? 0 : 1;
my $dX = $X < $x ? $W : 0;
my $dY = $Y < $y ? 0 : 0;
my $cx = $x+$dx; # Horizontal line corner x
my $cy = $Y+$dY+1/2; # Horizontal line corner y
my $xc = $X < $x ? q(black) : q(darkBlue); # Horizontal line color
my $x2 = $g->io == gateOuterOutput ? $cx - 1/2 : $cx;
$s->line(x1=>$X+$dX, x2=>$x2, y1=>$cy, y2=>$cy, stroke=>$xc); # Outgoing value along horizontal lines
my $yc = $Y < $y ? q(purple) : q(darkRed); # Vertical lines
if ($g->io != gateOuterOutput) # Incoming value along vertical line - not needed for outer output gates
{$s->line(x1=>$cx, x2=>$cx, y1=>$cy, y2=>$y+$dy, stroke=>$yc);
$s->circle(cx=>$cx, cy=>$cy, r=>0.06, fill=>"red"); # Line corner
$s->circle(cx=>$x2, cy=>$y+$dy, r=>0.04, fill=>"blue"); # Line entering gate
}
else # External output gate
{$s->circle(cx=>$x2, cy=>$y+$dy-1/2, r=>0.04, fill=>"blue"); # Line entering output
}
$s->circle(cx=>$X+$W, cy=>$cy, r=>0.04, fill=>"red"); # Line exiting gate
if (defined(my $v = $$values{$G->output}) and $g->io != gateOuterOutput)# Value of gate if known except for output gates written else where
{$s->text(
x => $cx,
y => $y+$dy+($X < $x ? 0.1 : -0.1),
fill => "black", stroke_width=>$fw, font_size=>$fs,
text_anchor => "middle",
$X < $x ? (dominant_baseline=>"hanging") : (),
cdata => $v ? "1" : "0");
}
}
}
}
my $t = $s->print;
return owf(fpe($options{svg}, q(svg)), $t) if $options{svg};
$t
}
sub Silicon::Chip::Simulation::printSvg($%) # Print simulation results as svg.
{my ($sim, %options) = @_; # Simulation, options
printSvg($sim->chip, %options, values=>$sim->values);
}
#D1 Basic Circuits # Some well known basic circuits.
sub n(*$) # Gate name from single index.
{my ($c, $i) = @_; # Gate name, bit number
!@_ or !ref($_[0]) or confess <<"END";
Call as a sub not as a method
END
"${c}_$i"
}
sub nn(*$$) # Gate name from double index.
{my ($c, $i, $j) = @_; # Gate name, word number, bit number
!@_ or !ref($_[0]) or confess confess <<"END";
Call as a sub not as a method
END
"${c}_${i}_$j"
}
#D2 Comparisons # Compare unsigned binary integers of specified bit widths.
sub compareEq($$$$%) # Compare two unsigned binary integers of a specified width returning B<1> if they are equal else B<0>.
{my ($chip, $output, $a, $b, %options) = @_; # Chip, name of component also the output bus, first integer, second integer, options
@_ >= 4 or confess "Four or more parameters";
my $o = $output;
my $A = sizeBits($chip, $a);
my $B = sizeBits($chip, $b);
$A == $B or confess <<"END" =~ s/\n(.)/ $1/gsr;
Input $a has width $A but input $b has width $B
END
$chip->nxor(n("$o.e", $_), n($a, $_), n($b, $_)) for 1..$B; # Test each bit pair for equality
$chip->andBits($o, "$o.e", bits=>$B); # All bits must be equal
$chip
}
sub compareGt($$$$%) # Compare two unsigned binary integers and return B<1> if the first integer is more than B<b> else B<0>.
{my ($chip, $output, $a, $b, %options) = @_; # Chip, name of component also the output bus, first integer, second integer, options
@_ >= 4 or confess "Four or more parameters";
my $o = $output;
my $A = sizeBits($chip, $a);
my $B = sizeBits($chip, $b);
$A == $B or confess <<"END" =~ s/\n(.)/ $1/gsr;
Input $a has width $A but input $b has width $B
END
$chip->nxor (n("$o.e", $_), n($a, $_), n($b, $_)) for 2..$B; # Test all but the lowest bit pair for equality
$chip->gt (n("$o.g", $_), n($a, $_), n($b, $_)) for 1..$B; # Test each bit pair for more than
for my $b(2..$B) # More than on one bit and all preceding bits are equal
{$chip->and(n("$o.c", $b),
{(map {$_=>n("$o.e", $_)} $b..$B), ($b-1)=>n("$o.g", $b-1)});
}
$chip->or ($o, {$B=>n("$o.g", $B), (map {($_-1)=>n("$o.c", $_)} 2..$B)}); # Any set bit indicates that B<a> is more than B<b>
$chip
}
sub compareLt($$$$%) # Compare two unsigned binary integers B<a>, B<b> of a specified width. Output B<out> is B<1> if B<a> is less than B<b> else B<0>.
{my ($chip, $output, $a, $b, %options) = @_; # Chip, name of component also the output bus, first integer, second integer, options
@_ >= 4 or confess "Four or more parameters";
my $A = sizeBits($chip, $a);
my $B = sizeBits($chip, $b);
$A == $B or confess <<"END" =~ s/\n(.)/ $1/gsr;
Input $a has width $A but input $b has width $B
END
my $o = $output;
$chip->nxor (n("$o.e", $_), n($a, $_), n($b, $_)) for 2..$B; # Test all but the lowest bit pair for equality
$chip->lt (n("$o.l", $_), n($a, $_), n($b, $_)) for 1..$B; # Test each bit pair for less than
for my $b(2..$B) # More than on one bit and all preceding bits are equal
{$chip->and(n("$o.c", $b),
{(map {$_=>n("$o.e", $_)} $b..$B), ($b-1)=>n("$o.l", $b-1)});
}
$chip->or ($o, {$B=>n("$o.l", $B), (map {($_-1)=>n("$o.c", $_)} 2..$B)}); # Any set bit indicates that B<a> is less than B<b>
$chip
}
sub chooseFromTwoWords($$$$$%) # Choose one of two words based on a bit. The first word is chosen if the bit is B<0> otherwise the second word is chosen.
{my ($chip, $output, $a, $b, $choose, %options) = @_; # Chip, name of component also the chosen word, the first word, the second word, the choosing bit, options
@_ >= 5 or confess "Five or more parameters";
my $o = $output;
my $A = sizeBits($chip, $a);
my $B = sizeBits($chip, $b);
$A == $B or confess <<"END" =~ s/\n(.)/ $1/gsr;
Input $a has width $A but input $b has width $B
END
$chip->not("$o.n", $choose); # Not of the choosing bit
for my $i(1..$B)
{$chip->and(n("$o.a", $i), [n($a, $i), "$o.n" ]); # Choose first word
$chip->and(n("$o.b", $i), [n($b, $i), $choose ]); # Choose second word
$chip->or (n($o, $i), [n("$o.a", $i), n("$o.b", $i)]); # Or results of choice
}
setSizeBits($chip, $o, $B); # Record bus size
$chip
}
sub enableWord($$$$%) # Output a word or zeros depending on a choice bit. The first word is chosen if the choice bit is B<1> otherwise all zeroes are chosen.
{my ($chip, $output, $a, $enable, %options) = @_; # Chip, name of component also the chosen word, the first word, the second word, the choosing bit, options
@_ >= 4 or confess "Four or more parameters";
my $o = $output;
my $B = sizeBits($chip, $a);
$chip->not ("$o.n", $enable); # Not of the choosing bit
$chip->bits("$o.z", $B, 0); # Zero value to transmit if choice bit is B<0>
for my $i(1..$B)
{$chip->and(n("$o.a", $i), [n($a, $i), $enable ]); # Choose second word
$chip->and(n("$o.b", $i), [n("$o.z", $i), "$o.n" ]); # Choose first word
$chip->or (n( $o, $i), [n("$o.a", $i), n("$o.b", $i)]); # Or results of choice
}
setSizeBits($chip, $o, $B); # Record bus size
$chip
}
#D2 Masks # Point masks and monotone masks. A point mask has a single B<1> in a sea of B<0>s as in B<00100>. A monotone mask has zero or more B<0>s followed by all B<1>s as in: B<00111>.
sub pointMaskToInteger($$$%) # Convert a mask B<i> known to have at most a single bit on - also known as a B<point mask> - to an output number B<a> representing the location in the mask of the bit set to B<1>. If no such bit exists in the point mask then output number B<a> is B<0>.
{my ($chip, $output, $input, %options) = @_; # Chip, output name, input mask, options
@_ >= 3 or confess "Three or more parameters";
my $B = sizeBits($chip, $input); # Bits in input bus
my $I = containingPowerOfTwo($B); # Bits in integer
my $i = $input; # The bits in the input mask
my $o = $output; # The name of the output bus
my %b;
for my $b(1..$B) # Bits in mask to bits in resulting number
{my $s = sprintf "%b", $b;
for my $p(1..length($s))
{$b{$p}{$b}++ if substr($s, -$p, 1);
}
}
for my $b(sort keys %b)
{$chip->or(n($o, $b), {map {$_=>n($i, $_)} sort keys $b{$b}->%*}); # Bits needed to drive a bit in the resulting number
}
setSizeBits $chip, $o, $I; # Size of resulting integer
$chip
}
sub integerToPointMask($$$%) # Convert an integer B<i> of specified width to a point mask B<m>. If the input integer is B<0> then the mask is all zeroes as well.
{my ($chip, $output, $input, %options) = @_; # Chip, output name, input mask, options
@_ >= 3 or confess "Three or more parameters";
my $bits = sizeBits($chip, $input); # Size of input integer in bits
my $B = 2**$bits-1;
my $o = $output; # Output mask
$chip->notBits("$o.n", $input); # Not of each input
for my $b(1..$B) # Each bit of the mask
{my @s = reverse split //, sprintf "%0${bits}b", $b; # Bits for this point in the mask
my %a;
for my $i(1..@s)
{$a{$i} = n($s[$i-1] ? 'i' : "$o.n", $i); # Combination of bits to enable this mask bit
}
$chip->and(n($output, $b), {%a}); # And to set this point in the mask
}
setSizeBits($chip, $output, $B); # Size of output bus
$chip
}
sub monotoneMaskToInteger($$$%) # Convert a monotone mask B<i> to an output number B<r> representing the location in the mask of the bit set to B<1>. If no such bit exists in the point then output in B<r> is B<0>.
{my ($chip, $output, $input, %options) = @_; # Chip, output name, input mask, options
@_ >= 3 or confess "Three or more parameters";
my $B = sizeBits($chip, $input);
my $I = containingPowerOfTwo($B);
my $o = $output;
my %b;
for my $b(1..$B)
{my $s = sprintf "%b", $b;
for my $p(1..length($s))
{$b{$p}{$b}++ if substr($s, -$p, 1);
}
}
$chip->not (n("$o.n", $_), n($input, $_)) for 1..$B-1; # Not of each input
$chip->continue(n("$o.a", 1), n($input, 1));
$chip->and (n("$o.a", $_), [n("$o.n", $_-1), n('i', $_)]) for 2..$B; # Look for trailing edge
for my $b(sort keys %b)
{$chip->or (n($o, $b), [map {n("$o.a", $_)} sort keys $b{$b}->%*]); # Bits needed to drive a bit in the resulting number
}
setSizeBits($chip, $o, $I);
$chip
}
sub monotoneMaskToPointMask($$$%) # Convert a monotone mask B<i> to a point mask B<o> representing the location in the mask of the first bit set to B<1>. If the monotone mask is all B<0>s then point mask is too.
{my ($chip, $output, $input, %options) = @_; # Chip, output name, input mask, options
@_ >= 3 or confess "Three or more parameters";
my $o = $output;
my $bits = sizeBits($chip, $input);
$chip->continue(n($o, 1), n($input, 1)); # The first bit in the monotone mask matches the first bit of the point mask
for my $b(2..$bits)
{$chip->xor(n($o, $b), n($input, $b-1), n($input, $b)); # Detect transition
}
setSizeBits($chip, $o, $bits);
$chip
}
sub integerToMonotoneMask($$$%) # Convert an integer B<i> of specified width to a monotone mask B<m>. If the input integer is B<0> then the mask is all zeroes. Otherwise the mask has B<i-1> leading zeroes followed by all ones thereafter.
{my ($chip, $output, $input, %options) = @_; # Chip, output name, input mask, options
@_ >= 3 or confess "Three or more parameters";
my $I = sizeBits($chip, $input);
my $B = 2**$I-1;
my $o = $output;
$chip->notBits("$o.n", $input); # Not of each input
for my $b(1..$B) # Each bit of the mask
{my @s = (reverse split //, sprintf "%0${I}b", $b); # Bits for this point in the mask
my %a;
for my $i(1..@s)
{$a{$i} = n($s[$i-1] ? $input : "$o.n", $i); # Choose either the input bit or the not of the input but depending on the number being converted to binary
}
$chip->and(n("$o.a", $b), {%a}); # Set at this point and beyond
$chip-> or(n($o, $b), [map {n("$o.a", $_)} 1..$b]); # Set mask
}
setSizeBits($chip, $o, $B);
$chip
}
sub chooseWordUnderMask($$$$%) # Choose one of a specified number of words B<w>, each of a specified width, using a point mask B<m> placing the selected word in B<o>. If no word is selected then B<o> will be zero.
{my ($chip, $output, $input, $mask, %options) = @_; # Chip, output, inputs, mask, options
@_ >= 3 or confess "Three or more parameters";
my $o = $output;
my ($words, $bits) = sizeWords($chip, $input);
my ($mi) = sizeBits ($chip, $mask);
$mi == $words or confess <<"END" =~ s/\n(.)/ $1/gsr;
Mask width $mi does not match number of words $words.
END
for my $w(1..$words) # And each bit of each word with the mask
{for my $b(1..$bits) # Bits in each word
{$chip->and(nn("$o.a", $w, $b), [n($mask, $w), nn($input, $w, $b)]);
}
}
for my $b(1..$bits) # Bits in each word
{$chip->or(n($o, $b), [map {nn("$o.a", $_, $b)} 1..$words]);
}
setSizeBits($chip, $o, $bits);
$chip
}
sub findWord($$$$%) # Choose one of a specified number of words B<w>, each of a specified width, using a key B<k>. Return a point mask B<o> indicating the locations of the key if found or or a mask equal to all zeroes if the key is not present.
{my ($chip, $output, $key, $words, %options) = @_; # Chip, found point mask, key, words to search, options
@_ >= 4 or confess "Four or more parameters";
my $o = $output;
my ($W, $B) = sizeWords($chip, $words);
my $bits = sizeBits ($chip, $key);
$B == $bits or confess <<"END" =~ s/\n(.)/ $1/gsr;
Number of bits in each word $B differs from words in key $bits.
END
for my $w(1..$W) # Input words
{$chip->compareEq(n($o, $w), n($words, $w), $key); # Compare each input word with the key to make a mask
}
setSizeBits($chip, $o, $W);
$chip
}
#D1 Simulate # Simulate the behavior of the L<chip> given a set of values on its input gates.
my sub setBit($*$%) # Set a single bit
{my ($chip, $name, $value, %options) = @_; # Chip, name of input gates, number to set to, options
@_ >= 3 or confess "Three or more parameters";
my $g = getGate($chip, $name);
my $t = $g->type;
$g->io == gateOuterInput or confess <<"END" =~ s/\n(.)/ $1/gsr;
Only outer input gates are setable. Gate $name is of type $t
END
my %i = ($name => $value ? $value : 0);
%i
}
sub setBits($*$%) # Set an array of input gates to a number prior to running a simulation.
{my ($chip, $name, $value, %options) = @_; # Chip, name of input gates, number to set to, options
@_ >= 3 or confess "Three or more parameters";
my $bits = sizeBits($chip, $name); # Size of bus
my $W = 2**$bits;
$value >= 0 or confess <<"END";
Value $value is less than 0
END
$value < $W or confess <<"END";
Value $value is greater then or equal to $W
END
my @b = reverse split //, sprintf "%0${bits}b", $value;
my %i = map {n($name, $_) => $b[$_-1]} 1..$bits;
%i
}
sub setWords($$@) # Set an array of arrays of gates to an array of numbers prior to running a simulation.
{my ($chip, $name, @values) = @_; # Chip, name of input gates, number of bits in each array element, numbers to set to
@_ >= 3 or confess "Three or more parameters";
my ($words, $bits) = sizeWords($chip, $name);
my %i;
my $W = 2**$words;
for my $w(1..$words) # Each word
{my $n = shift @values;
$n >= 0 or confess <<"END";
Value $n is less than 0
END
$n < $W or confess <<"END";
"Value $n is greater then or equal to $W";
END
my @b = split //, sprintf "%0${bits}b", $n;
for my $b(1..$bits) # Each bit
{$i{nn($name, $w, $b)} = $b[-$b];
}
}
%i
}
sub connectBits($*$*%) # Create a connection list connecting a set of output bits on the one chip to a set of input bits on another chip.
{my ($oc, $o, $ic, $i, %options) = @_; # First chip, name of gates on first chip, second chip, names of gates on second chip, options
@_ >= 4 or confess "Four or more parameters";
my %c;
my $O = sizeBits($oc, $o);
my $I = sizeBits($ic, $i);
$O == $I or confess <<"END";
Mismatch between size of bus $o at $O and bus $i at $I
END
for my $b(1..$I) # Bit to connect
{$c{n($o, $b)} = n($i, $b); # Connect bits
}
%c # Connection list
}
sub connectWords($*$*$$%) # Create a connection list connecting a set of words on the outer chip to a set of words on the inner chip.
{my ($oc, $o, $ic, $i, $words, $bits, %options) = @_; # First chip, name of gates on first chip, second chip, names of gates on second chip, number of words to connect, options
@_ >= 6 or confess "Six or more parameters";
my %c;
for my $w(1..$bits) # Word to connect
{for my $b(1..$bits) # Bit to connect
{$c{nn($o, $w, $b)} = nn($i, $w, $b); # Connection list
}
}
%c # Connection list
}
my sub merge($%) # Merge a L<chip> and all its sub L<chips> to make a single L<chip>.
{my ($chip, %options) = @_; # Chip, options
my $gates = getGates $chip; # Gates implementing the chip and all of its sub chips
setOuterGates ($chip, $gates); # Set the outer gates which are to be connected to in the real word
removeExcessIO($chip, $gates); # By pass and then remove all interior IO gates as they are no longer needed
my $c = newChip %$chip, %options, gates=>$gates, installs=>[]; # Create the new chip with all installs expanded
print($c, %options) if $options{print}; # Print the gates
printSvg ($c, %options) if $options{svg}; # Draw the gates using svg
checkIO $c; # Check all inputs are connected to valid gates and that all outputs are used
$c
}
my sub simulationResults($%) # Simulation results obtained by specifying the inputs to all the L<lgs> on the L<chip> and allowing its output L<lgs> to stabilize.
{my ($chip, %options) = @_; # Chip, hash of final values for each gate, options
genHash("Silicon::Chip::Simulation", # Simulation results
chip => $chip, # Chip being simulated
changed => $options{changed}, # Last time this gate changed
steps => $options{steps}, # Number of steps to reach stability
values => $options{values}, # Values of every output at point of stability
svg => $options{svg}, # Name of file containing svg drawing if requested
);
}
sub Silicon::Chip::Simulation::value($$%) # Get the value of a gate as seen in a simulation.
{my ($simulation, $name, %options) = @_; # Chip, gate, options
@_ >= 2 or confess "Two or more parameters";
$simulation->values->{$name} # Value of gate
}
my sub checkInputs($$%) # Check that an input value has been provided for every input pin on the chip.
{my ($chip, $inputs, %options) = @_; # Chip, inputs, hash of final values for each gate, options
for my $g(values $chip->gates->%*) # Each gate on chip
{if ($g->io == gateOuterInput) # Outer input gate
{my ($i) = values $g->inputs->%*; # Inputs
if (!defined($$inputs{$i})) # Check we have a corresponding input
{my $n = $g->output;
confess "No input value for input gate: $n\n";
}
}
}
}
sub Silicon::Chip::Simulation::bint($$%) # Represent the state of bits in the simulation results as an unsigned binary integer.
{my ($simulation, $output, %options) = @_; # Chip, name of gates on bus, options
@_ >= 2 or confess "Two or more parameters";
my $B = sizeBits($simulation->chip, $output);
my %v = $simulation->values->%*;
my @b;
for my $b(1..$B) # Bits
{push @b, $v{n $output, $b};
}
eval join '', '0b', reverse @b; # Convert to number
}
sub Silicon::Chip::Simulation::wordsToInteger($$%) # Represent the state of words in the simulation results as an array of unsigned binary integer.
{my ($simulation, $output, %options) = @_; # Chip, name of gates on bus, options
@_ >= 2 or confess "Two or more parameters";
my ($words, $bits) = sizeWords($simulation->chip, $output);
my %v = $simulation->values->%*;
my @w;
for my $w(1..$words) # Words
{my @b;
for my $b(1..$bits) # Bits
{push @b, $v{nn $output, $w, $b};
}
push @w, eval join '', '0b', reverse @b; # Convert to number
}
@w
}
sub Silicon::Chip::Simulation::wordXToInteger($$%) # Represent the state of words in the simulation results as an array of unsigned binary integer.
{my ($simulation, $output, %options) = @_; # Chip, name of gates on bus, options
@_ >= 2 or confess "Two or more parameters";
my ($words, $bits) = sizeWords($simulation->chip, $output);
my %v = $simulation->values->%*;
my @w;
for my $b(1..$bits) # Bits
{my @b;
for my $w(1..$words) # Words
{push @b, $v{nn $output, $w, $b};
}
push @w, eval join '', '0b', reverse @b; # Convert to number
}
@w
}
my sub simulationStep($$%) # One step in the simulation of the L<chip> after expansion of inner L<chips>.
{my ($chip, $values, %options) = @_; # Chip, current value of each gate, options
my $gates = $chip->gates; # Gates on chip
my %changes; # Changes made
for my $G(sort {$$gates{$a}->seq <=> $$gates{$b}->seq} keys %$gates) # Each gate in sub chip on definition order to get a repeatable order
{my $g = $$gates{$G}; # Address gate
my $t = $g->type; # Gate type
my $n = $g->output; # Gate name
my %i = $g->inputs->%*; # Inputs to gate
my @i = map {$$values{$i{$_}}} sort keys %i; # Values of inputs to gates in input pin name order
my $u = 0; # Number of undefined inputs
for my $i(@i)
{++$u unless defined $i;
}
if ($u == 0) # All inputs defined
{my $r; # Result of gate operation
if ($t =~ m(\Aand|nand\Z)i) # Elaborate and B<and> and B<nand> gates
{my $z = grep {!$_} @i; # Count zero inputs
$r = $z ? 0 : 1;
$r = !$r if $t =~ m(\Anand\Z)i;
}
elsif ($t =~ m(\A(input)\Z)i) # An B<input> gate takes its value from the list of inputs or from an output gate in an inner chip
{if (my @i = values $g->inputs->%*) # Get the value of the input gate from the current values
{my $n = $i[0];
$r = $$values{$n};
}
else
{confess "No driver for input gate: $n\n";
}
}
elsif ($t =~ m(\A(continue|nor|not|or|output)\Z)i) # Elaborate B<not>, B<or> or B<output> gate. A B<continue> gate places its single input unchanged on its output
{my $o = grep {$_} @i; # Count one inputs
$r = $o ? 1 : 0;
$r = $r ? 0 : 1 if $t =~ m(\Anor|not\Z)i;
}
elsif ($t =~ m(\A(nxor|xor)\Z)i) # Elaborate B<xor>
{@i == 2 or confess "$t gate: '$n' must have exactly two inputs\n";
$r = $i[0] ^ $i[1] ? 1 : 0;
$r = $r ? 0 : 1 if $t =~ m(\Anxor\Z)i;
}
elsif ($t =~ m(\A(gt|ngt)\Z)i) # Elaborate B<a> more than B<b> - the input pins are assumed to be sorted by name with the first pin as B<a> and the second as B<b>
{@i == 2 or confess "$t gate: '$n' must have exactly two inputs\n";
$r = $i[0] > $i[1] ? 1 : 0;
$r = $r ? 0 : 1 if $t =~ m(\Angt\Z)i;
}
elsif ($t =~ m(\A(lt|nlt)\Z)i) # Elaborate B<a> less than B<b> - the input pins are assumed to be sorted by name with the first pin as B<a> and the second as B<b>
{@i == 2 or confess "$t gate: '$n' must have exactly two inputs\n";
$r = $i[0] < $i[1] ? 1 : 0;
$r = $r ? 0 : 1 if $t =~ m(\Anlt\Z)i;
}
elsif ($t =~ m(\Aone\Z)i) # One
{@i == 0 or confess "$t gate: '$n' must have no inputs\n";
$r = 1;
}
elsif ($t =~ m(\Azero\Z)i) # Zero
{@i == 0 or confess "$t gate: '$n' must have no inputs\n";
$r = 0;
}
else # Unknown gate type
{confess "Need implementation for '$t' gates";
}
$changes{$G} = $r unless defined($$values{$G}) and $$values{$G} == $r; # Value computed by this gate
}
}
%changes
}
sub simulate($$%) # Simulate the action of the L<lgs> on a L<chip> for a given set of inputs until the output value of each L<lg> stabilizes.
{my ($chip, $inputs, %options) = @_; # Chip, Hash of input names to values, options
@_ >= 2 or confess "Two or more parameters";
my $c = merge($chip, %options); # Merge all the sub chips to make one chip with no sub chips
checkInputs($c, $inputs); # Confirm that there is an input value for every input to the chip
my %values = %$inputs; # The current set of values contains just the inputs at the start of the simulation
my %changed; # Last step on which this gate changed. We use this to order the gates on layout
my $T = maxSimulationSteps; # Maximum steps
for my $t(0..$T) # Steps in time
{my %changes = simulationStep $c, \%values; # Changes made
if (!keys %changes) # Keep going until nothing changes
{my $svg;
if ($options{svg}) # Draw the gates using svg with the final values attached
{$svg = printSvg $c, values=>\%values, changed=>\%changed,
steps=>$t, %options;
}
return simulationResults $chip, values=>\%values, changed=>\%changed, # Keep going until nothing changes
steps=>$t, svg=>$svg;
}
for my $c(keys %changes) # Update state of circuit
{$values{$c} = $changes{$c};
$changed{$c} = $t; # Last time we changed this gate
}
}
confess "Out of time after $T steps"; # Not enough steps available
}
#-------------------------------------------------------------------------------
# Export
#-------------------------------------------------------------------------------
use Exporter qw(import);
use vars qw(@ISA @EXPORT @EXPORT_OK %EXPORT_TAGS);
# containingFolder
@ISA = qw(Exporter);
@EXPORT = qw();
@EXPORT_OK = qw(connectBits connectWords n nn setBits setWords);
%EXPORT_TAGS = (all=>[@EXPORT, @EXPORT_OK]);
=pod
=encoding utf-8
=head1 Name
=head1 Synopsis
Create a chip to compare two 4 bit big endian unsigned integers for equality:
my $B = 4; # Number of bits
my $c = Silicon::Chip::newChip(title=>"$B Bit Equals"); # Create chip
$c->input ("a$_") for 1..$B; # First number
$c->input ("b$_") for 1..$B; # Second number
$c->nxor ("e$_", {1=>"a$_", 2=>"b$_"}) for 1..$B; # Test each bit for equality
$c->and ("and", {map{$_=>"e$_"} 1..$B}); # And tests together to get total equality
$c->output("out", "and"); # Output gate
my $s = $c->simulate({a1=>1, a2=>0, a3=>1, a4=>0, # Input gate values
b1=>1, b2=>0, b3=>1, b4=>0},
svg=>"svg/Equals$B"); # Svg drawing of layout
is_deeply($s->steps, 3); # Three steps
is_deeply($s->values->{out}, 1); # Out is 1 for equals
my $t = $c->simulate({a1=>1, a2=>1, a3=>1, a4=>0,
b1=>1, b2=>0, b3=>1, b4=>0});
is_deeply($t->values->{out}, 0); # Out is 0 for not equals
To obtain:
Other circuit diagrams can be seen in folder: L<lib/Silicon/svg|https://github.com/philiprbrenan/SiliconChip/tree/main/lib/Silicon/svg>
=head1 Description
Version 20231111.
The following sections describe the methods in each functional area of this
module. For an alphabetic listing of all methods by name see L<Index|/Index>.
=head1 Construct
Construct a L<Silicon|https://en.wikipedia.org/wiki/Silicon> L<chip|https://en.wikipedia.org/wiki/Integrated_circuit> using standard L<logic gates|https://en.wikipedia.org/wiki/Logic_gate>, components and sub chips combined via buses.
=head2 newChip (%options)
Parameter Description
1 %options Options
B<Example:>
if (1)
{my $c = Silicon::Chip::newChip; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
$c->one ("one");
$c->zero("zero");
$c->or ("or", [qw(one zero)]);
$c->and ("and", [qw(one zero)]);
$c->output("o1", "or");
$c->output("o2", "and");
my $s = $c->simulate({}, svg=>q(svg/oneZero));
is_deeply($s->steps , 3);
is_deeply($s->value("o1"), 1);
is_deeply($s->value("o2"), 0);
}
if (1) # Single AND gate
{my $c = Silicon::Chip::newChip; # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
$c->input ("i1");
$c->input ("i2");
$c->and ("and1", [qw(i1 i2)]);
$c->output("o", "and1");
my $s = $c->simulate({i1=>1, i2=>1});
ok($s->steps == 2);
ok($s->value("and1") == 1);
}
if (1) # 4 bit equal
{my $B = 4; # Number of bits
my $c = Silicon::Chip::newChip(title=><<"END"); # Create chip # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
$B Bit Equals
END
$c->input ("a$_") for 1..$B; # First number
$c->input ("b$_") for 1..$B; # Second number
$c->nxor ("e$_", "a$_", "b$_") for 1..$B; # Test each bit for equality
$c->and ("and", {map{$_=>"e$_"} 1..$B}); # And tests together to get total equality
$c->output("out", "and"); # Output gate
my $s = $c->simulate({a1=>1, a2=>0, a3=>1, a4=>0, # Input gate values
b1=>1, b2=>0, b3=>1, b4=>0},
svg=>q(svg/Equals)); # Svg drawing of layout
is_deeply($s->steps, 3); # Three steps
is_deeply($s->value("out"), 1); # Out is 1 for equals
my $t = $c->simulate({a1=>1, a2=>1, a3=>1, a4=>0,
b1=>1, b2=>0, b3=>1, b4=>0});
is_deeply($t->value("out"), 0); # Out is 0 for not equals
}
=head2 gate($chip, $type, $output, $input1, $input2)
A L<logic gate|https://en.wikipedia.org/wiki/Logic_gate> chosen from B<and|continue|gt|input|lt|nand|nor|not|nxor|one|or|output|xor|zero>. The gate type can be used as a method name, so B<-E<gt>gate("and",> can be reduced to B<-E<gt>and(>.
Parameter Description
1 $chip Chip
2 $type Gate type
3 $output Output name
4 $input1 Input from another gate
5 $input2 Input from another gate
B<Example:>
if (1) # Two AND gates driving an OR gate # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
{my $c = newChip;
$c->input ("i11");
$c->input ("i12");
$c->and ("and1", [qw(i11 i12)]);
$c->input ("i21");
$c->input ("i22");
$c->and ("and2", [qw(i21 i22 )]);
$c->or ("or", [qw(and1 and2)]);
$c->output( "o", "or");
my $s = $c->simulate({i11=>1, i12=>1, i21=>1, i22=>1});
ok($s->steps == 3);
ok($s->value("or") == 1);
$s = $c->simulate({i11=>1, i12=>0, i21=>1, i22=>1});
ok($s->steps == 3);
ok($s->value("or") == 1);
$s = $c->simulate({i11=>1, i12=>0, i21=>1, i22=>0});
ok($s->steps == 3);
ok($s->value("o") == 0);
}
=head2 connectInput($chip, $in, $to, %options)
Connect a previously defined input gate to the output of another previously gate on the same chip. This allows us to define a set of gates on the chip without having to know, first, all the names of the gates that will provide input to these gates.
Parameter Description
1 $chip Chip
2 $in Input gate
3 $to Gate to connect input gate to
4 %options Options
B<Example:>
if (1) # Internal input gate
{my $c = newChip();
$c->input ('i'); # Input
$c->input ('j'); # Internal input which we will connect to later
$c->output(qw(o j)); # Output
$c->connectInput(qw(j i)); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
my $s = $c->simulate({i=>1});
is_deeply($s->steps, 1);
is_deeply($s->value("j"), undef);
is_deeply($s->value("o"), 1);
}
if (1) # Internal input gate
{my @n = qw(3 2 1 2 3);
my $c = newChip();
$c->words('i', 2, @n); # Input
$c->outputWords(qw(o i)); # Output
my $s = $c->simulate({});
is_deeply($s->steps, 2);
is_deeply([$s->wordsToInteger("i")], [@n]);
}
if (1) # Internal input gate
{my @n = qw(3 2 1 2 3);
my $c = newChip();
$c->words('i', 2, @n); # Input
$c->outputWords(qw(o i)); # Output
my $s = $c->simulate({});
is_deeply($s->steps, 2);
is_deeply([$s->wordsToInteger("i")], [@n]);
}
=head2 Buses
A bus is an array of bits or an array of arrays of bits
=head3 Bits
An array of bits that can be manipulated via one name.
=head4 setSizeBits ($chip, $name, $bits, %options)
Set the size of a bits bus.
Parameter Description
1 $chip Chip
2 $name Bits bus name
3 $bits Options
4 %options
B<Example:>
if (1)
{my $c = newChip();
$c->setSizeBits ('i', 2); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
$c->setSizeWords('j', 3, 2);
is_deeply($c->sizeBits, {i => 2, j_1 => 2, j_2 => 2, j_3 => 2});
is_deeply($c->sizeWords, {j => [3, 2]});
}
=head4 bits($chip, $name, $bits, $value, %options)
Create a bus set to a specified number.
Parameter Description
1 $chip Chip
2 $name Name of bus
3 $bits Width in bits of bus
4 $value Value of bus
5 %options Options
B<Example:>
if (1)
{my $N = 4;
for my $i(0..2**$N-1)
{my $c = Silicon::Chip::newChip;
$c->bits ("c", $N, $i); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
$c->outputBits("o", "c");
my $s = $c->simulate({}, $i == 3 ? (svg=>q(svg/bits)) : ()); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
is_deeply($s->steps, 2);
is_deeply($s->bint("o"), $i);
}
}
=head4 inputBits   ($chip, $name, $bits, %options)
Create an B<input> bus made of bits.
Parameter Description
1 $chip Chip
2 $name Name of bus
3 $bits Width in bits of bus
4 %options Options
B<Example:>
if (1)
{my $W = 8;
my $i = newChip(name=>"not");
$i->inputBits('i', $W); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
$i->notBits (qw(n i));
$i->outputBits(qw(o n));
my $o = newChip(name=>"outer");
$o->inputBits ('a', $W); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
$o->outputBits(qw(A a));
$o->inputBits ('b', $W); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
$o->outputBits(qw(B b));
my %i = connectBits($i, 'i', $o, 'A');
my %o = connectBits($i, 'o', $o, 'b');
$o->install($i, {%i}, {%o});
my %d = setBits($o, 'a', 0b10110);
my $s = $o->simulate({%d}, svg=>q(svg/not));
is_deeply($s->bint('B'), 0b11101001);
}
=head4 outputBits  ($chip, $name, $input, %options)
Create an B<output> bus made of bits.
Parameter Description
1 $chip Chip
2 $name Name of bus
3 $input Name of inputs
4 %options Options
B<Example:>
if (1)
{my $W = 8;
my $i = newChip(name=>"not");
$i->inputBits('i', $W);
$i->notBits (qw(n i));
$i->outputBits(qw(o n)); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
my $o = newChip(name=>"outer");
$o->inputBits ('a', $W);
$o->outputBits(qw(A a)); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
$o->inputBits ('b', $W);
$o->outputBits(qw(B b)); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
my %i = connectBits($i, 'i', $o, 'A');
my %o = connectBits($i, 'o', $o, 'b');
$o->install($i, {%i}, {%o});
my %d = setBits($o, 'a', 0b10110);
my $s = $o->simulate({%d}, svg=>q(svg/not));
is_deeply($s->bint('B'), 0b11101001);
}
if (1)
{my @B = ((my $W = 4), (my $B = 2));
my $c = newChip();
$c->inputWords ('i', @B);
$c->andWords (qw(and i));
$c->andWordsX (qw(andX i));
$c-> orWords (qw( or i));
$c-> orWordsX (qw( orX i));
$c->notWords (qw(n i));
$c->outputBits (qw(And and)); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
$c->outputBits (qw(AndX andX)); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
$c->outputBits (qw(Or or)); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
$c->outputBits (qw(OrX orX)); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
$c->outputWords(qw(N n));
my %d = setWords($c, 'i', 0b00, 0b01, 0b10, 0b11);
my $s = $c->simulate({%d}, svg=>"svg/andOrWords$W");
is_deeply($s->bint('And'), 0b1000);
is_deeply($s->bint('AndX'), 0b0000);
is_deeply($s->bint('Or'), 0b1110);
is_deeply($s->bint('OrX'), 0b11);
is_deeply([$s->wordsToInteger('N')], [3, 2, 1, 0]);
}
=head4 notBits ($chip, $name, $input, %options)
Create a B<not> bus made of bits.
Parameter Description
1 $chip Chip
2 $name Name of bus
3 $input Name of inputs
4 %options Options
B<Example:>
if (1)
{my $W = 8;
my $i = newChip(name=>"not");
$i->inputBits('i', $W);
$i->notBits (qw(n i)); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
$i->outputBits(qw(o n));
my $o = newChip(name=>"outer");
$o->inputBits ('a', $W);
$o->outputBits(qw(A a));
$o->inputBits ('b', $W);
$o->outputBits(qw(B b));
my %i = connectBits($i, 'i', $o, 'A');
my %o = connectBits($i, 'o', $o, 'b');
$o->install($i, {%i}, {%o});
my %d = setBits($o, 'a', 0b10110);
my $s = $o->simulate({%d}, svg=>q(svg/not));
is_deeply($s->bint('B'), 0b11101001);
}
=head4 andBits ($chip, $name, $input, %options)
B<and> a bus made of bits.
Parameter Description
1 $chip Chip
2 $name Name of bus
3 $input Name of inputs
4 %options Options
B<Example:>
if (1)
{my $W = 8;
my $c = newChip();
$c-> inputBits('i', $W);
$c-> andBits(qw(and i)); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
$c-> orBits(qw(or i));
$c-> nandBits(qw(nand i));
$c-> norBits(qw(nor i));
$c->output (qw(And and));
$c->output (qw(Or or));
$c->output (qw(nAnd nand));
$c->output (qw(nOr nor));
my %d = setBits($c, 'i', 0b10110);
my $s = $c->simulate({%d}, svg=>q(svg/andOrBits));
is_deeply($s->value("And"), 0);
is_deeply($s->value("Or"), 1);
is_deeply($s->value("nAnd"), 1);
is_deeply($s->value("nOr"), 0);
}
=head4 nandBits($chip, $name, $input, %options)
B<nand> a bus made of bits.
Parameter Description
1 $chip Chip
2 $name Name of bus
3 $input Name of inputs
4 %options Options
B<Example:>
if (1)
{my $W = 8;
my $c = newChip();
$c-> inputBits('i', $W);
$c-> andBits(qw(and i));
$c-> orBits(qw(or i));
$c-> nandBits(qw(nand i)); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
$c-> norBits(qw(nor i));
$c->output (qw(And and));
$c->output (qw(Or or));
$c->output (qw(nAnd nand));
$c->output (qw(nOr nor));
my %d = setBits($c, 'i', 0b10110);
my $s = $c->simulate({%d}, svg=>q(svg/andOrBits));
is_deeply($s->value("And"), 0);
is_deeply($s->value("Or"), 1);
is_deeply($s->value("nAnd"), 1);
is_deeply($s->value("nOr"), 0);
}
=head4 orBits  ($chip, $name, $input, %options)
B<or> a bus made of bits.
Parameter Description
1 $chip Chip
2 $name Name of bus
3 $input Options
4 %options
B<Example:>
if (1)
{my $W = 8;
my $c = newChip();
$c-> inputBits('i', $W);
$c-> andBits(qw(and i));
$c-> orBits(qw(or i)); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
$c-> nandBits(qw(nand i));
$c-> norBits(qw(nor i));
$c->output (qw(And and));
$c->output (qw(Or or));
$c->output (qw(nAnd nand));
$c->output (qw(nOr nor));
my %d = setBits($c, 'i', 0b10110);
my $s = $c->simulate({%d}, svg=>q(svg/andOrBits));
is_deeply($s->value("And"), 0);
is_deeply($s->value("Or"), 1);
is_deeply($s->value("nAnd"), 1);
is_deeply($s->value("nOr"), 0);
}
=head4 norBits ($chip, $name, $input, %options)
B<nor> a bus made of bits.
Parameter Description
1 $chip Chip
2 $name Name of bus
3 $input Options
4 %options
B<Example:>
if (1)
{my $W = 8;
my $c = newChip();
$c-> inputBits('i', $W);
$c-> andBits(qw(and i));
$c-> orBits(qw(or i));
$c-> nandBits(qw(nand i));
$c-> norBits(qw(nor i)); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
$c->output (qw(And and));
$c->output (qw(Or or));
$c->output (qw(nAnd nand));
$c->output (qw(nOr nor));
my %d = setBits($c, 'i', 0b10110);
my $s = $c->simulate({%d}, svg=>q(svg/andOrBits));
is_deeply($s->value("And"), 0);
is_deeply($s->value("Or"), 1);
is_deeply($s->value("nAnd"), 1);
is_deeply($s->value("nOr"), 0);
}
=head3 Words
An array of arrays of bits that can be manipulated via one name.
=head4 setSizeWords($chip, $name, $words, $bits, %options)
Set the size of a bits bus.
Parameter Description
1 $chip Chip
2 $name Bits bus name
3 $words Words
4 $bits Bits per word
5 %options Options
B<Example:>
if (1)
{my $c = newChip();
$c->setSizeBits ('i', 2);
$c->setSizeWords('j', 3, 2); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
is_deeply($c->sizeBits, {i => 2, j_1 => 2, j_2 => 2, j_3 => 2});
is_deeply($c->sizeWords, {j => [3, 2]});
}
=head4 words   ($chip, $name, $bits, @values)
Create a word bus set to specified numbers.
Parameter Description
1 $chip Chip
2 $name Name of bus
3 $bits Width in bits of each word
4 @values Values of words
B<Example:>
if (1) # Internal input gate
{my @n = qw(3 2 1 2 3);
my $c = newChip();
$c->words('i', 2, @n); # Input # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
$c->outputWords(qw(o i)); # Output
my $s = $c->simulate({});
is_deeply($s->steps, 2);
is_deeply([$s->wordsToInteger("i")], [@n]);
}
if (1) # Internal input gate
{my @n = qw(3 2 1 2 3);
my $c = newChip();
$c->words('i', 2, @n); # Input # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
$c->outputWords(qw(o i)); # Output
my $s = $c->simulate({});
is_deeply($s->steps, 2);
is_deeply([$s->wordsToInteger("i")], [@n]);
}
=head4 inputWords  ($chip, $name, $words, $bits, %options)
Create an B<input> bus made of words.
Parameter Description
1 $chip Chip
2 $name Name of bus
3 $words Width in words of bus
4 $bits Width in bits of each word on bus
5 %options Options
B<Example:>
if (1)
{my @b = ((my $W = 4), (my $B = 3));
my $c = newChip();
$c->inputWords ('i', @b); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
$c->outputWords(qw(o i));
my %d = setWords($c, 'i', 0b000, 0b001, 0b010, 0b011);
my $s = $c->simulate({%d}, svg=>"svg/words$W");
is_deeply([$s->wordsToInteger('o')], [0..3]);
is_deeply([$s->wordXToInteger('o')], [10, 12, 0]);
}
=head4 outputWords ($chip, $name, $input, %options)
Create an B<output> bus made of words.
Parameter Description
1 $chip Chip
2 $name Name of bus
3 $input Name of inputs
4 %options Options
B<Example:>
if (1)
{my @b = ((my $W = 4), (my $B = 3));
my $c = newChip();
$c->inputWords ('i', @b);
$c->outputWords(qw(o i)); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
my %d = setWords($c, 'i', 0b000, 0b001, 0b010, 0b011);
my $s = $c->simulate({%d}, svg=>"svg/words$W");
is_deeply([$s->wordsToInteger('o')], [0..3]);
is_deeply([$s->wordXToInteger('o')], [10, 12, 0]);
}
=head4 notWords($chip, $name, $input, %options)
Create a B<not> bus made of words.
Parameter Description
1 $chip Chip
2 $name Name of bus
3 $input Name of inputs
4 %options Options
B<Example:>
if (1)
{my @B = ((my $W = 4), (my $B = 2));
my $c = newChip();
$c->inputWords ('i', @B);
$c->andWords (qw(and i));
$c->andWordsX (qw(andX i));
$c-> orWords (qw( or i));
$c-> orWordsX (qw( orX i));
$c->notWords (qw(n i)); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
$c->outputBits (qw(And and));
$c->outputBits (qw(AndX andX));
$c->outputBits (qw(Or or));
$c->outputBits (qw(OrX orX));
$c->outputWords(qw(N n));
my %d = setWords($c, 'i', 0b00, 0b01, 0b10, 0b11);
my $s = $c->simulate({%d}, svg=>"svg/andOrWords$W");
is_deeply($s->bint('And'), 0b1000);
is_deeply($s->bint('AndX'), 0b0000);
is_deeply($s->bint('Or'), 0b1110);
is_deeply($s->bint('OrX'), 0b11);
is_deeply([$s->wordsToInteger('N')], [3, 2, 1, 0]);
}
=head4 andWords($chip, $name, $input, %options)
B<and> a bus made of words to produce a single word.
Parameter Description
1 $chip Chip
2 $name Name of bus
3 $input Name of inputs
4 %options Options
B<Example:>
if (1)
{my @B = ((my $W = 4), (my $B = 2));
my $c = newChip();
$c->inputWords ('i', @B);
$c->andWords (qw(and i)); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
$c->andWordsX (qw(andX i));
$c-> orWords (qw( or i));
$c-> orWordsX (qw( orX i));
$c->notWords (qw(n i));
$c->outputBits (qw(And and));
$c->outputBits (qw(AndX andX));
$c->outputBits (qw(Or or));
$c->outputBits (qw(OrX orX));
$c->outputWords(qw(N n));
my %d = setWords($c, 'i', 0b00, 0b01, 0b10, 0b11);
my $s = $c->simulate({%d}, svg=>"svg/andOrWords$W");
is_deeply($s->bint('And'), 0b1000);
is_deeply($s->bint('AndX'), 0b0000);
is_deeply($s->bint('Or'), 0b1110);
is_deeply($s->bint('OrX'), 0b11);
is_deeply([$s->wordsToInteger('N')], [3, 2, 1, 0]);
}
if (1)
{my @b = ((my $W = 4), (my $B = 3));
my $c = newChip();
$c->inputWords ('i', @b);
$c->outputWords(qw(o i));
my %d = setWords($c, 'i', 0b000, 0b001, 0b010, 0b011);
my $s = $c->simulate({%d}, svg=>"svg/words$W");
is_deeply([$s->wordsToInteger('o')], [0..3]);
is_deeply([$s->wordXToInteger('o')], [10, 12, 0]);
}
=head4 andWordsX   ($chip, $name, $input, %options)
B<and> a bus made of words by and-ing the corresponding bits in each word to make a single word.
Parameter Description
1 $chip Chip
2 $name Name of bus
3 $input Name of inputs
4 %options Options
B<Example:>
if (1)
{my @B = ((my $W = 4), (my $B = 2));
my $c = newChip();
$c->inputWords ('i', @B);
$c->andWords (qw(and i));
$c->andWordsX (qw(andX i)); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
$c-> orWords (qw( or i));
$c-> orWordsX (qw( orX i));
$c->notWords (qw(n i));
$c->outputBits (qw(And and));
$c->outputBits (qw(AndX andX));
$c->outputBits (qw(Or or));
$c->outputBits (qw(OrX orX));
$c->outputWords(qw(N n));
my %d = setWords($c, 'i', 0b00, 0b01, 0b10, 0b11);
my $s = $c->simulate({%d}, svg=>"svg/andOrWords$W");
is_deeply($s->bint('And'), 0b1000);
is_deeply($s->bint('AndX'), 0b0000);
is_deeply($s->bint('Or'), 0b1110);
is_deeply($s->bint('OrX'), 0b11);
is_deeply([$s->wordsToInteger('N')], [3, 2, 1, 0]);
}
=head4 orWords ($chip, $name, $input, %options)
B<or> a bus made of words to produce a single word.
Parameter Description
1 $chip Chip
2 $name Name of bus
3 $input Name of inputs
4 %options Options
B<Example:>
if (1)
{my @B = ((my $W = 4), (my $B = 2));
my $c = newChip();
$c->inputWords ('i', @B);
$c->andWords (qw(and i));
$c->andWordsX (qw(andX i));
$c-> orWords (qw( or i)); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
$c-> orWordsX (qw( orX i));
$c->notWords (qw(n i));
$c->outputBits (qw(And and));
$c->outputBits (qw(AndX andX));
$c->outputBits (qw(Or or));
$c->outputBits (qw(OrX orX));
$c->outputWords(qw(N n));
my %d = setWords($c, 'i', 0b00, 0b01, 0b10, 0b11);
my $s = $c->simulate({%d}, svg=>"svg/andOrWords$W");
is_deeply($s->bint('And'), 0b1000);
is_deeply($s->bint('AndX'), 0b0000);
is_deeply($s->bint('Or'), 0b1110);
is_deeply($s->bint('OrX'), 0b11);
is_deeply([$s->wordsToInteger('N')], [3, 2, 1, 0]);
}
if (1)
{my @b = ((my $W = 4), (my $B = 3));
my $c = newChip();
$c->inputWords ('i', @b);
$c->outputWords(qw(o i));
my %d = setWords($c, 'i', 0b000, 0b001, 0b010, 0b011);
my $s = $c->simulate({%d}, svg=>"svg/words$W");
is_deeply([$s->wordsToInteger('o')], [0..3]);
is_deeply([$s->wordXToInteger('o')], [10, 12, 0]);
}
=head4 orWordsX($chip, $name, $input, %options)
B<or> a bus made of words by or-ing the corresponding bits in each word to make a single word.
Parameter Description
1 $chip Chip
2 $name Name of bus
3 $input Name of inputs
4 %options Options
B<Example:>
if (1)
{my @B = ((my $W = 4), (my $B = 2));
my $c = newChip();
$c->inputWords ('i', @B);
$c->andWords (qw(and i));
$c->andWordsX (qw(andX i));
$c-> orWords (qw( or i));
$c-> orWordsX (qw( orX i)); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
$c->notWords (qw(n i));
$c->outputBits (qw(And and));
$c->outputBits (qw(AndX andX));
$c->outputBits (qw(Or or));
$c->outputBits (qw(OrX orX));
$c->outputWords(qw(N n));
my %d = setWords($c, 'i', 0b00, 0b01, 0b10, 0b11);
my $s = $c->simulate({%d}, svg=>"svg/andOrWords$W");
is_deeply($s->bint('And'), 0b1000);
is_deeply($s->bint('AndX'), 0b0000);
is_deeply($s->bint('Or'), 0b1110);
is_deeply($s->bint('OrX'), 0b11);
is_deeply([$s->wordsToInteger('N')], [3, 2, 1, 0]);
}
=head2 Install
Install a chip within a chip as a sub chip.
=head3 install ($chip, $subChip, $inputs, $outputs, %options)
Install a L<chip|https://en.wikipedia.org/wiki/Integrated_circuit> within another L<chip|https://en.wikipedia.org/wiki/Integrated_circuit> specifying the connections between the inner and outer L<chip|https://en.wikipedia.org/wiki/Integrated_circuit>. The same L<chip|https://en.wikipedia.org/wiki/Integrated_circuit> can be installed multiple times as each L<chip|https://en.wikipedia.org/wiki/Integrated_circuit> description is read only.
Parameter Description
1 $chip Outer chip
2 $subChip Inner chip
3 $inputs Inputs of inner chip to to outputs of outer chip
4 $outputs Outputs of inner chip to inputs of outer chip
5 %options Options
B<Example:>
if (1) # Install one chip inside another chip, specifically one chip that performs NOT is installed once to flip a value
{my $i = newChip(name=>"not");
$i-> inputBits('i', 1);
$i-> notBits(qw(n i));
$i->outputBits(qw(o n));
my $o = newChip(name=>"outer");
$o->inputBits('i', 1); $o->outputBits(qw(n i));
$o->inputBits('I', 1); $o->outputBits(qw(N I));
my %i = connectBits($i, 'i', $o, 'n');
my %o = connectBits($i, 'o', $o, 'I');
$o->install($i, {%i}, {%o}); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
my %d = $o->setBits('i', 1);
my $s = $o->simulate({%d}, printOff=>"dump/not1", svg=>"svg/not1");
is_deeply($s->steps, 2);
is_deeply($s->values, {"(not 1 n_1)"=>0, "i_1"=>1, "N_1"=>0 });
}
=head1 Visualize
Visualize the L<chip|https://en.wikipedia.org/wiki/Integrated_circuit> in various ways.
=head2 print   ($chip, %options)
Parameter Description
1 $chip Chip
2 %options Gates
B<Example:>
if (1)
{my $c = Silicon::Chip::newChip(title=>"And gate");
$c->input ("i1");
$c->input ("i2");
$c->and ("and1", [qw(i1 i2)]);
$c->output("o", "and1");
my $s = $c->simulate({i1=>1, i2=>1});
is_deeply($c->print, <<END); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
i1 : input i1
i2 : input i2
and1 : and i1 i2
o : output and1
END
is_deeply($s->print, <<END); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
i1 : 1 input i1
i2 : 1 input i2
and1 : 1 and i1 i2
o : 1 output and1
END
ok($s->printSvg ne $c->printSvg);
}
=head2 Silicon::Chip::Simulation::print($sim, %options)
Print simulation results as text.
Parameter Description
1 $sim Simulation
2 %options Options
B<Example:>
if (1)
{my $c = Silicon::Chip::newChip(title=>"And gate");
$c->input ("i1");
$c->input ("i2");
$c->and ("and1", [qw(i1 i2)]);
$c->output("o", "and1");
my $s = $c->simulate({i1=>1, i2=>1});
is_deeply($c->print, <<END);
i1 : input i1
i2 : input i2
and1 : and i1 i2
o : output and1
END
is_deeply($s->print, <<END);
i1 : 1 input i1
i2 : 1 input i2
and1 : 1 and i1 i2
o : 1 output and1
END
ok($s->printSvg ne $c->printSvg);
}
=head2 printSvg($chip, %options)
Dump the L<logic gates|https://en.wikipedia.org/wiki/Logic_gate> on a L<chip|https://en.wikipedia.org/wiki/Integrated_circuit> as an L<Scalar Vector Graphics|https://en.wikipedia.org/wiki/Scalable_Vector_Graphics> drawing to help visualize the structure of the L<chip|https://en.wikipedia.org/wiki/Integrated_circuit>.
Parameter Description
1 $chip Chip
2 %options Options
B<Example:>
if (1)
{my $c = Silicon::Chip::newChip(title=>"And gate");
$c->input ("i1");
$c->input ("i2");
$c->and ("and1", [qw(i1 i2)]);
$c->output("o", "and1");
my $s = $c->simulate({i1=>1, i2=>1});
is_deeply($c->print, <<END);
i1 : input i1
i2 : input i2
and1 : and i1 i2
o : output and1
END
is_deeply($s->print, <<END);
i1 : 1 input i1
i2 : 1 input i2
and1 : 1 and i1 i2
o : 1 output and1
END
ok($s->printSvg ne $c->printSvg); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
}
=head2 Silicon::Chip::Simulation::printSvg ($sim, %options)
Print simulation results as svg.
Parameter Description
1 $sim Simulation
2 %options Options
B<Example:>
if (1)
{my $c = Silicon::Chip::newChip(title=>"And gate");
$c->input ("i1");
$c->input ("i2");
$c->and ("and1", [qw(i1 i2)]);
$c->output("o", "and1");
my $s = $c->simulate({i1=>1, i2=>1});
is_deeply($c->print, <<END);
i1 : input i1
i2 : input i2
and1 : and i1 i2
o : output and1
END
is_deeply($s->print, <<END);
i1 : 1 input i1
i2 : 1 input i2
and1 : 1 and i1 i2
o : 1 output and1
END
ok($s->printSvg ne $c->printSvg);
}
=head1 Basic Circuits
Some well known basic circuits.
=head2 n   ($c, $i)
Gate name from single index.
Parameter Description
1 $c Gate name
2 $i Bit number
B<Example:>
if (1)
{is_deeply( n(a,1), "a_1"); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
is_deeply(nn(a,1,2), "a_1_2");
}
=head2 nn  ($c, $i, $j)
Gate name from double index.
Parameter Description
1 $c Gate name
2 $i Word number
3 $j Bit number
B<Example:>
if (1)
{is_deeply( n(a,1), "a_1");
is_deeply(nn(a,1,2), "a_1_2"); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
}
=head2 Comparisons
Compare unsigned binary integers of specified bit widths.
=head3 compareEq   ($chip, $output, $a, $b, %options)
Compare two unsigned binary integers of a specified width returning B<1> if they are equal else B<0>.
Parameter Description
1 $chip Chip
2 $output Name of component also the output bus
3 $a First integer
4 $b Second integer
5 %options Options
B<Example:>
if (1) # Compare unsigned integers
{my $B = 2;
my $c = Silicon::Chip::newChip(title=><<"END");
$B Bit Compare Equal
END
$c->inputBits($_, $B) for qw(a b); # First and second numbers
$c->compareEq(qw(o a b)); # Compare equals # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
$c->output (qw(out o)); # Comparison result
for my $i(0..2**$B-1) # Each possible number
{for my $j(0..2**$B-1) # Each possible number
{my %a = $c->setBits('a', $i); # Number a
my %b = $c->setBits('b', $j); # Number b
my $s = $c->simulate({%a, %b}, $i==1&&$j==1?(svg=>"svg/CompareEq$B"):()); # Svg drawing of layout
is_deeply($s->value("out"), $i == $j ? 1 : 0); # Equal
is_deeply($s->steps, 3); # Number of steps to stability
}
}
}
=head3 compareGt   ($chip, $output, $a, $b, %options)
Compare two unsigned binary integers and return B<1> if the first integer is more than B<b> else B<0>.
Parameter Description
1 $chip Chip
2 $output Name of component also the output bus
3 $a First integer
4 $b Second integer
5 %options Options
B<Example:>
if (1) # Compare 8 bit unsigned integers 'a' > 'b' - the pins used to input 'a' must be alphabetically less than those used for 'b'
{my $B = 3;
my $c = Silicon::Chip::newChip(title=><<END);
$B Bit Compare more than
END
$c->inputBits($_, $B) for qw(a b); # First and second numbers
$c->compareGt(qw(o a b)); # Compare more than # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
$c->output (qw(out o)); # Comparison result
for my $i(0..2**$B-1) # Each possible number
{for my $j(0..2**$B-1) # Each possible number
{my %a = $c->setBits('a', $i); # Number a
my %b = $c->setBits('b', $j); # Number b
my $s = $c->simulate({%a, %b}, $i==2&&$j==1?(svg=>"svg/CompareGt$B"):()); # Svg drawing of layout
is_deeply($s->value("out"), $i > $j ? 1 : 0); # More than
is_deeply($s->steps, 4); # Number of steps to stability
}
}
}
=head3 compareLt   ($chip, $output, $a, $b, %options)
Compare two unsigned binary integers B<a>, B<b> of a specified width. Output B<out> is B<1> if B<a> is less than B<b> else B<0>.
Parameter Description
1 $chip Chip
2 $output Name of component also the output bus
3 $a First integer
4 $b Second integer
5 %options Options
B<Example:>
if (1) # Compare 8 bit unsigned integers 'a' < 'b' - the pins used to input 'a' must be alphabetically less than those used for 'b'
{my $B = 3;
my $c = Silicon::Chip::newChip(title=><<"END");
$B Bit Compare Less Than
END
$c->inputBits($_, $B) for qw(a b); # First and second numbers
$c->compareLt(qw(o a b)); # Compare less than # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
$c->output (qw(out o)); # Comparison result
for my $i(0..2**$B-1) # Each possible number
{for my $j(0..2**$B-1) # Each possible number
{my %a = $c->setBits('a', $i); # Number a
my %b = $c->setBits('b', $j); # Number b
my $s = $c->simulate({%a, %b}, $i==1&&$j==2?(svg=>"svg/CompareLt$B"):()); # Svg drawing of layout
is_deeply($s->value("out"), $i < $j ? 1 : 0); # More than
is_deeply($s->steps, 4); # Number of steps to stability
}
}
}
=head3 chooseFromTwoWords  ($chip, $output, $a, $b, $choose, %options)
Choose one of two words based on a bit. The first word is chosen if the bit is B<0> otherwise the second word is chosen.
Parameter Description
1 $chip Chip
2 $output Name of component also the chosen word
3 $a The first word
4 $b The second word
5 $choose The choosing bit
6 %options Options
B<Example:>
if (1)
{my $B = 4;
my $c = newChip();
$c->inputBits('a', $B); # First word
$c->inputBits('b', $B); # Second word
$c->input ('c'); # Chooser
$c->chooseFromTwoWords(qw(o a b c)); # Generate gates # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
$c->outputBits('out', 'o'); # Result
my %a = setBits($c, 'a', 0b0011);
my %b = setBits($c, 'b', 0b1100);
my $s = $c->simulate({%a, %b, c=>1}, svg=>q(svg/chooseFromTwoWords)); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
is_deeply($s->steps, 4);
is_deeply($s->bint('out'), 0b1100);
my $t = $c->simulate({%a, %b, c=>0});
is_deeply($t->steps, 4);
is_deeply($t->bint('out'), 0b0011);
}
=head3 enableWord  ($chip, $output, $a, $enable, %options)
Output a word or zeros depending on a choice bit. The first word is chosen if the choice bit is B<1> otherwise all zeroes are chosen.
Parameter Description
1 $chip Chip
2 $output Name of component also the chosen word
3 $a The first word
4 $enable The second word
5 %options The choosing bit
B<Example:>
if (1)
{my $B = 4;
my $c = newChip();
$c->inputBits ('a', $B); # Word
$c->input ('c'); # Choice bit
$c->enableWord(qw(o a c)); # Generate gates # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
$c->outputBits(qw(out o)); # Result
my %a = setBits($c, 'a', 3);
my $s = $c->simulate({%a, c=>1}, svg=>q(svg/enableWord)); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
is_deeply($s->steps, 4);
is_deeply($s->bint('out'), 3);
my $t = $c->simulate({%a, c=>0});
is_deeply($t->steps, 4);
is_deeply($t->bint('out'), 0);
}
=head2 Masks
Point masks and monotone masks. A point mask has a single B<1> in a sea of B<0>s as in B<00100>. A monotone mask has zero or more B<0>s followed by all B<1>s as in: B<00111>.
=head3 pointMaskToInteger  ($chip, $output, $input, %options)
Convert a mask B<i> known to have at most a single bit on - also known as a B<point mask> - to an output number B<a> representing the location in the mask of the bit set to B<1>. If no such bit exists in the point mask then output number B<a> is B<0>.
Parameter Description
1 $chip Chip
2 $output Output name
3 $input Input mask
4 %options Options
B<Example:>
if (1)
{my $B = 4;
my $N = 2**$B-1;
my $c = Silicon::Chip::newChip(title=><<"END");
$B bits point mask to integer
END
$c->inputBits (qw( i), $N); # Mask with no more than one bit on
$c->pointMaskToInteger(qw(o i)); # Convert # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
$c->outputBits (qw(out o)); # Mask with no more than one bit on
for my $i(0..$N) # Each position of mask
{my %i = setBits($c, 'i', $i ? 1<<($i-1) : 0); # Point in each position with zero representing no position
my $s = $c->simulate(\%i, $i == 5 ? (svg=>"svg/point$B") : ());
is_deeply($s->steps, 2);
my %o = $s->values->%*; # Output bits
my $n = eval join '', '0b', map {$o{n(o,$_)}} reverse 1..$B; # Output bits as number
is_deeply($n, $i);
}
}
=head3 integerToPointMask  ($chip, $output, $input, %options)
Convert an integer B<i> of specified width to a point mask B<m>. If the input integer is B<0> then the mask is all zeroes as well.
Parameter Description
1 $chip Chip
2 $output Output name
3 $input Input mask
4 %options Options
B<Example:>
if (1)
{my $B = 3;
my $N = 2**$B-1;
my $c = Silicon::Chip::newChip(title=><<"END");
$B bit integer to $N bits monotone mask.
END
$c->inputBits (qw( i), $B); # Input bus
$c->integerToPointMask(qw(m i)); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
$c->outputBits (qw(o m));
for my $i(0..$N) # Each position of mask
{my %i = setBits($c, 'i', $i);
my $s = $c->simulate(\%i, $i == 5 ? (svg=>"svg/integerToMontoneMask$B"):());
is_deeply($s->steps, 3);
my $r = $s->bint('o'); # Mask values
is_deeply($r, $i ? 1<<($i-1) : 0); # Expected mask
}
}
=head3 monotoneMaskToInteger   ($chip, $output, $input, %options)
Convert a monotone mask B<i> to an output number B<r> representing the location in the mask of the bit set to B<1>. If no such bit exists in the point then output in B<r> is B<0>.
Parameter Description
1 $chip Chip
2 $output Output name
3 $input Input mask
4 %options Options
B<Example:>
if (1)
{my $B = 4;
my $N = 2**$B-1;
my $c = Silicon::Chip::newChip(title=><<"END");
$N bits monotone mask to $B bit integer
END
$c->inputBits ('i', $N);
$c->monotoneMaskToInteger(qw(m i)); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
$c->outputBits (qw(o m));
for my $i(0..$N-1) # Each monotone mask
{my %i = setBits($c, 'i', $i > 0 ? 1<<$i-1 : 0);
my $s = $c->simulate(\%i,
$i == 5 ? (svg=>"svg/monotoneMaskToInteger$B") : ()); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
is_deeply($s->steps, 4);
is_deeply($s->bint('m'), $i);
}
}
=head3 monotoneMaskToPointMask ($chip, $output, $input, %options)
Convert a monotone mask B<i> to a point mask B<o> representing the location in the mask of the first bit set to B<1>. If the monotone mask is all B<0>s then point mask is too.
Parameter Description
1 $chip Chip
2 $output Output name
3 $input Input mask
4 %options Options
B<Example:>
if (1)
{my $B = 4;
my $c = newChip();
$c->inputBits('m', $B); # Monotone mask
$c->monotoneMaskToPointMask(qw(o m)); # Generate gates # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
$c->outputBits('out', 'o'); # Point mask
for my $i(0..$B)
{my %m = $c->setBits('m', eval '0b'.(1 x $i).('0' x ($B-$i)));
my $s = $c->simulate({%m});
is_deeply($s->steps, 2);
is_deeply($s->bint('out'), $i ? (1<<($B-1)) / (1<<($i-1)) : 0);
}
}
=head3 integerToMonotoneMask   ($chip, $output, $input, %options)
Convert an integer B<i> of specified width to a monotone mask B<m>. If the input integer is B<0> then the mask is all zeroes. Otherwise the mask has B<i-1> leading zeroes followed by all ones thereafter.
Parameter Description
1 $chip Chip
2 $output Output name
3 $input Input mask
4 %options Options
B<Example:>
if (1)
{my $B = 4;
my $N = 2**$B-1;
my $c = Silicon::Chip::newChip(title=><<"END");
Convert $B bit integer to $N bit monotone mask
END
$c->inputBits ('i', $B); # Input gates
$c->integerToMonotoneMask(qw(m i)); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
$c->outputBits (qw(o m)); # Output gates
for my $i(0..$N) # Each position of mask
{my %i = setBits($c, 'i', $i); # The number to convert
my $s = $c->simulate(\%i, $i == 2 ? (svg=>"svg/integerToMontoneMask$B"):());
is_deeply($s->steps, 4);
is_deeply($s->bint('o'), $i > 0 ? ((1<<$N)-1)>>($i-1)<<($i-1) : 0);# Expected mask
}
}
=head3 chooseWordUnderMask ($chip, $output, $input, $mask, %options)
Choose one of a specified number of words B<w>, each of a specified width, using a point mask B<m> placing the selected word in B<o>. If no word is selected then B<o> will be zero.
Parameter Description
1 $chip Chip
2 $output Output
3 $input Inputs
4 $mask Mask
5 %options Options
B<Example:>
if (1)
{my $B = 3; my $W = 4;
my $c = Silicon::Chip::newChip(title=><<"END");
Choose one of $W words of $B bits
END
$c->inputWords ('w', $W, $B);
$c->inputBits ('m', $W);
$c->chooseWordUnderMask(qw(W w m)); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
$c->outputBits (qw(o W));
my %i = setWords($c, 'w', 0b000, 0b001, 0b010, 0b0100);
my %m = setBits ($c, 'm', 1<<2); # Choose the third word
my $s = $c->simulate({%i, %m}, svg=>"svg/choose_${W}_$B");
is_deeply($s->steps, 3);
is_deeply($s->bint('o'), 0b010);
}
=head3 findWord($chip, $output, $key, $words, %options)
Choose one of a specified number of words B<w>, each of a specified width, using a key B<k>. Return a point mask B<o> indicating the locations of the key if found or or a mask equal to all zeroes if the key is not present.
Parameter Description
1 $chip Chip
2 $output Found point mask
3 $key Key
4 $words Words to search
5 %options Options
B<Example:>
if (1)
{my $B = 3; my $W = 2**$B-1;
my $c = Silicon::Chip::newChip(title=><<END);
Search $W words of $B bits
END
$c->inputBits ('k', $B); # Search key
$c->inputWords('w', 2**$B-1, $B); # Words to search
$c->findWord (qw(m k w)); # Find the word # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
$c->outputBits(qw(M m)); # Output mask
my %w = setWords($c, 'w', reverse 1..$W);
for my $k(0..$W) # Each possible key
{my %k = setBits($c, 'k', $k);
my $s = $c->simulate({%k, %w}, $k == 3 ? (svg=>q(svg/findWord)) : ()); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
is_deeply($s->steps, 3);
is_deeply($s->bint('M'),$k ? 2**($W-$k) : 0);
}
}
=head1 Simulate
Simulate the behavior of the L<chip|https://en.wikipedia.org/wiki/Integrated_circuit> given a set of values on its input gates.
=head2 setBits ($chip, $name, $value, %options)
Set an array of input gates to a number prior to running a simulation.
Parameter Description
1 $chip Chip
2 $name Name of input gates
3 $value Number to set to
4 %options Options
B<Example:>
if (1) # Compare two 4 bit unsigned integers 'a' > 'b' - the pins used to input 'a' must be alphabetically less than those used for 'b'
{my $B = 4; # Number of bits
my $c = Silicon::Chip::newChip(title=><<"END");
$B Bit Compare
END
$c->inputBits("a", $B); # First number
$c->inputBits("b", $B); # Second number
$c->nxor (n(e,$_), n(a,$_), n(b,$_)) for 1..$B-1; # Test each bit for equality
$c->gt (n(g,$_), n(a,$_), n(b,$_)) for 1..$B; # Test each bit pair for greater
for my $b(2..$B)
{$c->and(n(c,$b), [(map {n(e, $_)} 1..$b-1), n(g,$b)]); # Greater on one bit and all preceding bits are equal
}
$c->or ("or", [n(g,1), (map {n(c, $_)} 2..$B)]); # Any set bit indicates that 'a' is more than 'b'
$c->output("out", "or"); # Output 1 if a > b else 0
my %a = $c->setBits('a', 0); # Number a # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
my %b = $c->setBits('b', 0); # Number b # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
my $s = $c->simulate({%a, %b, n(a,2)=>1, n(b,2)=>1}); # Two equal numbers
is_deeply($s->value("out"), 0);
my $t = $c->simulate({%a, %b, n(a,2)=>1}, svg=>q(svg/Compare)); # Svg drawing of layout
is_deeply($t->value("out"), 1);
}
if (1)
{my $B = 3; my $W = 4;
my $c = Silicon::Chip::newChip(title=><<"END");
Choose one of $W words of $B bits
END
$c->inputWords ('w', $W, $B);
$c->inputBits ('m', $W);
$c->chooseWordUnderMask(qw(W w m));
$c->outputBits (qw(o W));
my %i = setWords($c, 'w', 0b000, 0b001, 0b010, 0b0100);
my %m = setBits ($c, 'm', 1<<2); # Choose the third word # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
my $s = $c->simulate({%i, %m}, svg=>"svg/choose_${W}_$B");
is_deeply($s->steps, 3);
is_deeply($s->bint('o'), 0b010);
}
=head2 setWords($chip, $name, @values)
Set an array of arrays of gates to an array of numbers prior to running a simulation.
Parameter Description
1 $chip Chip
2 $name Name of input gates
3 @values Number of bits in each array element
B<Example:>
if (1)
{my $B = 3; my $W = 4;
my $c = Silicon::Chip::newChip(title=><<"END");
Choose one of $W words of $B bits
END
$c->inputWords ('w', $W, $B);
$c->inputBits ('m', $W);
$c->chooseWordUnderMask(qw(W w m));
$c->outputBits (qw(o W));
my %i = setWords($c, 'w', 0b000, 0b001, 0b010, 0b0100); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
my %m = setBits ($c, 'm', 1<<2); # Choose the third word
my $s = $c->simulate({%i, %m}, svg=>"svg/choose_${W}_$B");
is_deeply($s->steps, 3);
is_deeply($s->bint('o'), 0b010);
}
=head2 connectBits ($oc, $o, $ic, $i, %options)
Create a connection list connecting a set of output bits on the one chip to a set of input bits on another chip.
Parameter Description
1 $oc First chip
2 $o Name of gates on first chip
3 $ic Second chip
4 $i Names of gates on second chip
5 %options Options
B<Example:>
if (1) # Install one chip inside another chip, specifically one chip that performs NOT is installed once to flip a value
{my $i = newChip(name=>"not");
$i-> inputBits('i', 1);
$i-> notBits(qw(n i));
$i->outputBits(qw(o n));
my $o = newChip(name=>"outer");
$o->inputBits('i', 1); $o->outputBits(qw(n i));
$o->inputBits('I', 1); $o->outputBits(qw(N I));
my %i = connectBits($i, 'i', $o, 'n'); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
my %o = connectBits($i, 'o', $o, 'I'); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
$o->install($i, {%i}, {%o});
my %d = $o->setBits('i', 1);
my $s = $o->simulate({%d}, printOff=>"dump/not1", svg=>"svg/not1");
is_deeply($s->steps, 2);
is_deeply($s->values, {"(not 1 n_1)"=>0, "i_1"=>1, "N_1"=>0 });
}
=head2 connectWords($oc, $o, $ic, $i, $words, $bits, %options)
Create a connection list connecting a set of words on the outer chip to a set of words on the inner chip.
Parameter Description
1 $oc First chip
2 $o Name of gates on first chip
3 $ic Second chip
4 $i Names of gates on second chip
5 $words Number of words to connect
6 $bits Options
7 %options
B<Example:>
if (1) # Install one chip inside another chip, specifically one chip that performs NOT is installed three times sequentially to flip a value
{my $i = newChip(name=>"not");
$i-> inputWords('i', 1, 1);
$i-> notWords(qw(n i));
$i->outputWords(qw(o n));
my $o = newChip(name=>"outer");
$o->inputWords('i', 1, 1); $o->output(nn('n', 1, 1), nn('i', 1, 1));
$o->inputWords('I', 1, 1); $o->output(nn('N', 1, 1), nn('I', 1, 1));
my %i = connectWords($i, 'i', $o, 'n', 1, 1); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
my %o = connectWords($i, 'o', $o, 'I', 1, 1); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
$o->install($i, {%i}, {%o});
my %d = $o->setWords('i', 1);
my $s = $o->simulate({%d}, printOff=>"dump/not1", svg=>"svg/not1");
is_deeply($s->steps, 2);
is_deeply($s->values, { "(not 1 n_1_1)" => 0, "i_1_1" => 1, "N_1_1" => 0 });
}
=head2 Silicon::Chip::Simulation::value($simulation, $name, %options)
Get the value of a gate as seen in a simulation
Parameter Description
1 $simulation Chip
2 $name Gate
3 %options Options
B<Example:>
if (1) # Internal input gate
{my $c = newChip();
$c->input ('i'); # Input
$c->input ('j'); # Internal input which we will connect to later
$c->output(qw(o j)); # Output
$c->connectInput(qw(j i));
my $s = $c->simulate({i=>1});
is_deeply($s->steps, 1);
is_deeply($s->value("j"), undef);
is_deeply($s->value("o"), 1);
}
if (1) # Internal input gate
{my @n = qw(3 2 1 2 3);
my $c = newChip();
$c->words('i', 2, @n); # Input
$c->outputWords(qw(o i)); # Output
my $s = $c->simulate({});
is_deeply($s->steps, 2);
is_deeply([$s->wordsToInteger("i")], [@n]);
}
if (1) # Internal input gate
{my @n = qw(3 2 1 2 3);
my $c = newChip();
$c->words('i', 2, @n); # Input
$c->outputWords(qw(o i)); # Output
my $s = $c->simulate({});
is_deeply($s->steps, 2);
is_deeply([$s->wordsToInteger("i")], [@n]);
}
=head2 Silicon::Chip::Simulation::bint ($simulation, $output, %options)
Represent the state of bits in the simulation results as an unsigned binary integer.
Parameter Description
1 $simulation Chip
2 $output Name of gates on bus
3 %options Options
B<Example:>
if (1)
{my $W = 8;
my $i = newChip(name=>"not");
$i->inputBits('i', $W);
$i->notBits (qw(n i));
$i->outputBits(qw(o n));
my $o = newChip(name=>"outer");
$o->inputBits ('a', $W);
$o->outputBits(qw(A a));
$o->inputBits ('b', $W);
$o->outputBits(qw(B b));
my %i = connectBits($i, 'i', $o, 'A');
my %o = connectBits($i, 'o', $o, 'b');
$o->install($i, {%i}, {%o});
my %d = setBits($o, 'a', 0b10110);
my $s = $o->simulate({%d}, svg=>q(svg/not));
is_deeply($s->bint('B'), 0b11101001);
}
=head2 Silicon::Chip::Simulation::wordsToInteger   ($simulation, $output, %options)
Represent the state of words in the simulation results as an array of unsigned binary integer.
Parameter Description
1 $simulation Chip
2 $output Name of gates on bus
3 %options Options
B<Example:>
if (1)
{my @b = ((my $W = 4), (my $B = 3));
my $c = newChip();
$c->inputWords ('i', @b);
$c->outputWords(qw(o i));
my %d = setWords($c, 'i', 0b000, 0b001, 0b010, 0b011);
my $s = $c->simulate({%d}, svg=>"svg/words$W");
is_deeply([$s->wordsToInteger('o')], [0..3]);
is_deeply([$s->wordXToInteger('o')], [10, 12, 0]);
}
=head2 Silicon::Chip::Simulation::wordXToInteger   ($simulation, $output, %options)
Represent the state of words in the simulation results as an array of unsigned binary integer.
Parameter Description
1 $simulation Chip
2 $output Name of gates on bus
3 %options Options
B<Example:>
if (1)
{my @b = ((my $W = 4), (my $B = 3));
my $c = newChip();
$c->inputWords ('i', @b);
$c->outputWords(qw(o i));
my %d = setWords($c, 'i', 0b000, 0b001, 0b010, 0b011);
my $s = $c->simulate({%d}, svg=>"svg/words$W");
is_deeply([$s->wordsToInteger('o')], [0..3]);
is_deeply([$s->wordXToInteger('o')], [10, 12, 0]);
}
=head2 simulate($chip, $inputs, %options)
Simulate the action of the L<logic gates|https://en.wikipedia.org/wiki/Logic_gate> on a L<chip|https://en.wikipedia.org/wiki/Integrated_circuit> for a given set of inputs until the output value of each L<logic gate|https://en.wikipedia.org/wiki/Logic_gate> stabilizes.
Parameter Description
1 $chip Chip
2 $inputs Hash of input names to values
3 %options Options
B<Example:>
if (1)
{my $i = newChip(name=>"inner");
$i->input ("Ii");
$i->not ("In", "Ii");
$i->output( "Io", "In");
my $o = newChip(name=>"outer");
$o->input ("Oi1");
$o->output("Oo1", "Oi1");
$o->input ("Oi2");
$o->output("Oo2", "Oi2");
$o->input ("Oi3");
$o->output("Oo3", "Oi3");
$o->input ("Oi4");
$o->output("Oo", "Oi4");
$o->install($i, {Ii=>"Oo1"}, {Io=>"Oi2"});
$o->install($i, {Ii=>"Oo2"}, {Io=>"Oi3"});
$o->install($i, {Ii=>"Oo3"}, {Io=>"Oi4"});
my $s = $o->simulate({Oi1=>1}, printOff=>"dump/not3", svg=>"svg/not3"); # 𝗘𝘅𝗮𝗺𝗽𝗹𝗲
is_deeply($s->value("Oo"), 0);
is_deeply($s->steps, 4);
}
=head1 Hash Definitions
=head2 Silicon::Chip Definition
Chip description
=head3 Output fields
=head4 gateSeq
Gate sequence number - this allows us to display the gates in the order they were defined ti simplify the understanding of drawn layouts
=head4 gates
Gates in chip
=head4 installs
Chips installed within the chip
=head4 name
Name of chip
=head4 sizeBits
Sizes of buses
=head4 sizeWords
Sizes of buses
=head4 title
Title if known
=head1 Private Methods
=head2 AUTOLOAD($chip, @options)
Autoload by L<logic gate|https://en.wikipedia.org/wiki/Logic_gate> name to provide a more readable way to specify the L<logic gates|https://en.wikipedia.org/wiki/Logic_gate> on a L<chip|https://en.wikipedia.org/wiki/Integrated_circuit>.
Parameter Description
1 $chip Chip
2 @options Options
=head1 Index
1 L<andBits|/andBits> - B<and> a bus made of bits.
2 L<andWords|/andWords> - B<and> a bus made of words to produce a single word.
3 L<andWordsX|/andWordsX> - B<and> a bus made of words by and-ing the corresponding bits in each word to make a single word.
4 L<AUTOLOAD|/AUTOLOAD> - Autoload by L<logic gate|https://en.wikipedia.org/wiki/Logic_gate> name to provide a more readable way to specify the L<logic gates|https://en.wikipedia.org/wiki/Logic_gate> on a L<chip|https://en.wikipedia.org/wiki/Integrated_circuit>.
5 L<bits|/bits> - Create a bus set to a specified number.
6 L<chooseFromTwoWords|/chooseFromTwoWords> - Choose one of two words based on a bit.
7 L<chooseWordUnderMask|/chooseWordUnderMask> - Choose one of a specified number of words B<w>, each of a specified width, using a point mask B<m> placing the selected word in B<o>.
8 L<compareEq|/compareEq> - Compare two unsigned binary integers of a specified width returning B<1> if they are equal else B<0>.
9 L<compareGt|/compareGt> - Compare two unsigned binary integers and return B<1> if the first integer is more than B<b> else B<0>.
10 L<compareLt|/compareLt> - Compare two unsigned binary integers B<a>, B<b> of a specified width.
11 L<connectBits|/connectBits> - Create a connection list connecting a set of output bits on the one chip to a set of input bits on another chip.
12 L<connectInput|/connectInput> - Connect a previously defined input gate to the output of another previously gate on the same chip.
13 L<connectWords|/connectWords> - Create a connection list connecting a set of words on the outer chip to a set of words on the inner chip.
14 L<enableWord|/enableWord> - Output a word or zeros depending on a choice bit.
15 L<findWord|/findWord> - Choose one of a specified number of words B<w>, each of a specified width, using a key B<k>.
16 L<gate|/gate> - A L<logic gate|https://en.wikipedia.org/wiki/Logic_gate> chosen from B<and|continue|gt|input|lt|nand|nor|not|nxor|one|or|output|xor|zero>.
17 L<inputBits|/inputBits> - Create an B<input> bus made of bits.
18 L<inputWords|/inputWords> - Create an B<input> bus made of words.
19 L<install|/install> - Install a L<chip|https://en.wikipedia.org/wiki/Integrated_circuit> within another L<chip|https://en.wikipedia.org/wiki/Integrated_circuit> specifying the connections between the inner and outer L<chip|https://en.wikipedia.org/wiki/Integrated_circuit>.
20 L<integerToMonotoneMask|/integerToMonotoneMask> - Convert an integer B<i> of specified width to a monotone mask B<m>.
21 L<integerToPointMask|/integerToPointMask> - Convert an integer B<i> of specified width to a point mask B<m>.
22 L<monotoneMaskToInteger|/monotoneMaskToInteger> - Convert a monotone mask B<i> to an output number B<r> representing the location in the mask of the bit set to B<1>.
23 L<monotoneMaskToPointMask|/monotoneMaskToPointMask> - Convert a monotone mask B<i> to a point mask B<o> representing the location in the mask of the first bit set to B<1>.
24 L<n|/n> - Gate name from single index.
25 L<nandBits|/nandBits> - B<nand> a bus made of bits.
26 L<newChip|/newChip> - Create a new L<chip|https://en.wikipedia.org/wiki/Integrated_circuit>.
27 L<nn|/nn> - Gate name from double index.
28 L<norBits|/norBits> - B<nor> a bus made of bits.
29 L<notBits|/notBits> - Create a B<not> bus made of bits.
30 L<notWords|/notWords> - Create a B<not> bus made of words.
31 L<orBits|/orBits> - B<or> a bus made of bits.
32 L<orWords|/orWords> - B<or> a bus made of words to produce a single word.
33 L<orWordsX|/orWordsX> - B<or> a bus made of words by or-ing the corresponding bits in each word to make a single word.
34 L<outputBits|/outputBits> - Create an B<output> bus made of bits.
35 L<outputWords|/outputWords> - Create an B<output> bus made of words.
36 L<pointMaskToInteger|/pointMaskToInteger> - Convert a mask B<i> known to have at most a single bit on - also known as a B<point mask> - to an output number B<a> representing the location in the mask of the bit set to B<1>.
37 L<print|/print> - Dump the L<logic gates|https://en.wikipedia.org/wiki/Logic_gate> present on a L<chip|https://en.wikipedia.org/wiki/Integrated_circuit>.
38 L<printSvg|/printSvg> - Dump the L<logic gates|https://en.wikipedia.org/wiki/Logic_gate> on a L<chip|https://en.wikipedia.org/wiki/Integrated_circuit> as an L<Scalar Vector Graphics|https://en.wikipedia.org/wiki/Scalable_Vector_Graphics> drawing to help visualize the structure of the L<chip|https://en.wikipedia.org/wiki/Integrated_circuit>.
39 L<setBits|/setBits> - Set an array of input gates to a number prior to running a simulation.
40 L<setSizeBits|/setSizeBits> - Set the size of a bits bus.
41 L<setSizeWords|/setSizeWords> - Set the size of a bits bus.
42 L<setWords|/setWords> - Set an array of arrays of gates to an array of numbers prior to running a simulation.
43 L<Silicon::Chip::Simulation::bint|/Silicon::Chip::Simulation::bint> - Represent the state of bits in the simulation results as an unsigned binary integer.
44 L<Silicon::Chip::Simulation::print|/Silicon::Chip::Simulation::print> - Print simulation results as text.
45 L<Silicon::Chip::Simulation::printSvg|/Silicon::Chip::Simulation::printSvg> - Print simulation results as svg.
46 L<Silicon::Chip::Simulation::value|/Silicon::Chip::Simulation::value> - Get the value of a gate as seen in a simulation
47 L<Silicon::Chip::Simulation::wordsToInteger|/Silicon::Chip::Simulation::wordsToInteger> - Represent the state of words in the simulation results as an array of unsigned binary integer.
48 L<Silicon::Chip::Simulation::wordXToInteger|/Silicon::Chip::Simulation::wordXToInteger> - Represent the state of words in the simulation results as an array of unsigned binary integer.
49 L<simulate|/simulate> - Simulate the action of the L<logic gates|https://en.wikipedia.org/wiki/Logic_gate> on a L<chip|https://en.wikipedia.org/wiki/Integrated_circuit> for a given set of inputs until the output value of each L<logic gate|https://en.wikipedia.org/wiki/Logic_gate> stabilizes.
50 L<words|/words> - Create a word bus set to specified numbers.
=head1 Installation
This module is written in 100% Pure Perl and, thus, it is easy to read,
comprehend, use, modify and install via B<cpan>:
sudo cpan install Silicon::Chip
=head1 Author
L<philiprbrenan@gmail.com|mailto:philiprbrenan@gmail.com>
=head1 Copyright
Copyright (c) 2016-2023 Philip R Brenan.
This module is free software. It may be used, redistributed and/or modified
under the same terms as Perl itself.
=cut
#D0 Tests # Tests and examples
goto finish if caller; # Skip testing if we are being called as a module
clearFolder(q(svg), 99); # Clear the output svg folder
eval "use Test::More tests=>531;";
eval "Test::More->builder->output('/dev/null');" if -e q(/home/phil/);
eval {goto latest};
if (1) #Tn #Tnn
{is_deeply( n(a,1), "a_1");
is_deeply(nn(a,1,2), "a_1_2");
}
if (1) # Unused output
{my $c = Silicon::Chip::newChip;
$c->input( "i1");
eval {$c->simulate({i1=>1})};
ok($@ =~ m(Output from gate 'i1' is never used)i);
}
if (1) # Gate already specified
{my $c = Silicon::Chip::newChip;
$c->input("i1");
eval {$c->input("i1")};
ok($@ =~ m(Gate: 'i1' has already been specified));
}
#latest:;
if (1) # Check all inputs have values
{my $c = Silicon::Chip::newChip;
$c->input ("i1");
$c->input ("i2");
$c->and ("and", [qw(i1 i2)]);
$c->output("o", q(and));
eval {$c->simulate({i1=>1, i22=>1})};
ok($@ =~ m(No input value for input gate: i2)i);
}
#latest:;
if (1) # Check each input to each gate receives output from another gate
{my $c = Silicon::Chip::newChip;
$c->input("i1");
$c->input("i2");
$c->and ("and1", [qw(i1 i2)]);
$c->output( "o", q(an1));
eval {$c->simulate({i1=>1, i2=>1})};
ok($@ =~ m(No output driving input 'an1' on 'output' gate 'o')i);
}
#latest:;
if (1) #Tzero
{my $c = Silicon::Chip::newChip;
$c->zero ("z");
$c->output("o", "z");
my $s = $c->simulate({}, svg=>q(svg/zero));
is_deeply($s->steps, 2);
is_deeply($s->value("o"), 0);
}
#latest:;
if (1) #Tone
{my $c = Silicon::Chip::newChip;
$c->one ("o");
$c->output("O", "o");
my $s = $c->simulate({}, svg=>q(svg/one));
is_deeply($s->steps , 2);
is_deeply($s->value("O"), 1);
}
#latest:;
if (1) #TnewChip
{my $c = Silicon::Chip::newChip;
$c->one ("one");
$c->zero("zero");
$c->or ("or", [qw(one zero)]);
$c->and ("and", [qw(one zero)]);
$c->output("o1", "or");
$c->output("o2", "and");
my $s = $c->simulate({}, svg=>q(svg/oneZero));
is_deeply($s->steps , 3);
is_deeply($s->value("o1"), 1);
is_deeply($s->value("o2"), 0);
}
#latest:;
if (1) #Tbits
{my $N = 4;
for my $i(0..2**$N-1)
{my $c = Silicon::Chip::newChip;
$c->bits ("c", $N, $i);
$c->outputBits("o", "c");
my $s = $c->simulate({}, $i == 3 ? (svg=>q(svg/bits)) : ());
is_deeply($s->steps, 2);
is_deeply($s->bint("o"), $i);
}
}
#latest:;
if (1) #TnewChip # Single AND gate
{my $c = Silicon::Chip::newChip;
$c->input ("i1");
$c->input ("i2");
$c->and ("and1", [qw(i1 i2)]);
$c->output("o", "and1");
my $s = $c->simulate({i1=>1, i2=>1});
ok($s->steps == 2);
ok($s->value("and1") == 1);
}
#latest:;
if (1) #TSilicon::Chip::Simulation::print #Tprint #TSilicon::Chip::Simulation::printSvg #TprintSvg
{my $c = Silicon::Chip::newChip(title=>"And gate");
$c->input ("i1");
$c->input ("i2");
$c->and ("and1", [qw(i1 i2)]);
$c->output("o", "and1");
my $s = $c->simulate({i1=>1, i2=>1});
is_deeply($c->print, <<END);
i1 : input i1
i2 : input i2
and1 : and i1 i2
o : output and1
END
is_deeply($s->print, <<END);
i1 : 1 input i1
i2 : 1 input i2
and1 : 1 and i1 i2
o : 1 output and1
END
ok($s->printSvg ne $c->printSvg);
}
#latest:;
if (1) # Three AND gates in a tree
{my $c = Silicon::Chip::newChip;
$c->input( "i11");
$c->input( "i12");
$c->and( "and1", [qw(i11 i12)]);
$c->input( "i21");
$c->input( "i22");
$c->and( "and2", [qw(i21 i22)]);
$c->and( "and", [qw(and1 and2)]);
$c->output( "o", "and");
my $s = $c->simulate({i11=>1, i12=>1, i21=>1, i22=>1});
ok($s->steps == 3);
ok($s->value("and") == 1);
$s = $c->simulate({i11=>1, i12=>0, i21=>1, i22=>1});
ok($s->steps == 3);
ok($s->value("and") == 0);
}
#latest:;
if (1) #Tgate # Two AND gates driving an OR gate
{my $c = newChip;
$c->input ("i11");
$c->input ("i12");
$c->and ("and1", [qw(i11 i12)]);
$c->input ("i21");
$c->input ("i22");
$c->and ("and2", [qw(i21 i22 )]);
$c->or ("or", [qw(and1 and2)]);
$c->output( "o", "or");
my $s = $c->simulate({i11=>1, i12=>1, i21=>1, i22=>1});
ok($s->steps == 3);
ok($s->value("or") == 1);
$s = $c->simulate({i11=>1, i12=>0, i21=>1, i22=>1});
ok($s->steps == 3);
ok($s->value("or") == 1);
$s = $c->simulate({i11=>1, i12=>0, i21=>1, i22=>0});
ok($s->steps == 3);
ok($s->value("o") == 0);
}
#latest:;
if (1) # 4 bit equal #TnewChip
{my $B = 4; # Number of bits
my $c = Silicon::Chip::newChip(title=><<"END"); # Create chip
$B Bit Equals
END
$c->input ("a$_") for 1..$B; # First number
$c->input ("b$_") for 1..$B; # Second number
$c->nxor ("e$_", "a$_", "b$_") for 1..$B; # Test each bit for equality
$c->and ("and", {map{$_=>"e$_"} 1..$B}); # And tests together to get total equality
$c->output("out", "and"); # Output gate
my $s = $c->simulate({a1=>1, a2=>0, a3=>1, a4=>0, # Input gate values
b1=>1, b2=>0, b3=>1, b4=>0},
svg=>q(svg/Equals)); # Svg drawing of layout
is_deeply($s->steps, 3); # Three steps
is_deeply($s->value("out"), 1); # Out is 1 for equals
my $t = $c->simulate({a1=>1, a2=>1, a3=>1, a4=>0,
b1=>1, b2=>0, b3=>1, b4=>0});
is_deeply($t->value("out"), 0); # Out is 0 for not equals
}
#latest:;
if (1) #TsetBits # Compare two 4 bit unsigned integers 'a' > 'b' - the pins used to input 'a' must be alphabetically less than those used for 'b'
{my $B = 4; # Number of bits
my $c = Silicon::Chip::newChip(title=><<"END");
$B Bit Compare
END
$c->inputBits("a", $B); # First number
$c->inputBits("b", $B); # Second number
$c->nxor (n(e,$_), n(a,$_), n(b,$_)) for 1..$B-1; # Test each bit for equality
$c->gt (n(g,$_), n(a,$_), n(b,$_)) for 1..$B; # Test each bit pair for greater
for my $b(2..$B)
{$c->and(n(c,$b), [(map {n(e, $_)} 1..$b-1), n(g,$b)]); # Greater on one bit and all preceding bits are equal
}
$c->or ("or", [n(g,1), (map {n(c, $_)} 2..$B)]); # Any set bit indicates that 'a' is more than 'b'
$c->output("out", "or"); # Output 1 if a > b else 0
my %a = $c->setBits('a', 0); # Number a
my %b = $c->setBits('b', 0); # Number b
my $s = $c->simulate({%a, %b, n(a,2)=>1, n(b,2)=>1}); # Two equal numbers
is_deeply($s->value("out"), 0);
my $t = $c->simulate({%a, %b, n(a,2)=>1}, svg=>q(svg/Compare)); # Svg drawing of layout
is_deeply($t->value("out"), 1);
}
#latest:;
if (1) #TcompareEq # Compare unsigned integers
{my $B = 2;
my $c = Silicon::Chip::newChip(title=><<"END");
$B Bit Compare Equal
END
$c->inputBits($_, $B) for qw(a b); # First and second numbers
$c->compareEq(qw(o a b)); # Compare equals
$c->output (qw(out o)); # Comparison result
for my $i(0..2**$B-1) # Each possible number
{for my $j(0..2**$B-1) # Each possible number
{my %a = $c->setBits('a', $i); # Number a
my %b = $c->setBits('b', $j); # Number b
my $s = $c->simulate({%a, %b}, $i==1&&$j==1?(svg=>"svg/CompareEq$B"):()); # Svg drawing of layout
is_deeply($s->value("out"), $i == $j ? 1 : 0); # Equal
is_deeply($s->steps, 3); # Number of steps to stability
}
}
}
#latest:;
if (1) #TcompareGt # Compare 8 bit unsigned integers 'a' > 'b' - the pins used to input 'a' must be alphabetically less than those used for 'b'
{my $B = 3;
my $c = Silicon::Chip::newChip(title=><<END);
$B Bit Compare more than
END
$c->inputBits($_, $B) for qw(a b); # First and second numbers
$c->compareGt(qw(o a b)); # Compare more than
$c->output (qw(out o)); # Comparison result
for my $i(0..2**$B-1) # Each possible number
{for my $j(0..2**$B-1) # Each possible number
{my %a = $c->setBits('a', $i); # Number a
my %b = $c->setBits('b', $j); # Number b
my $s = $c->simulate({%a, %b}, $i==2&&$j==1?(svg=>"svg/CompareGt$B"):()); # Svg drawing of layout
is_deeply($s->value("out"), $i > $j ? 1 : 0); # More than
is_deeply($s->steps, 4); # Number of steps to stability
}
}
}
#latest:;
if (1) #TcompareLt # Compare 8 bit unsigned integers 'a' < 'b' - the pins used to input 'a' must be alphabetically less than those used for 'b'
{my $B = 3;
my $c = Silicon::Chip::newChip(title=><<"END");
$B Bit Compare Less Than
END
$c->inputBits($_, $B) for qw(a b); # First and second numbers
$c->compareLt(qw(o a b)); # Compare less than
$c->output (qw(out o)); # Comparison result
for my $i(0..2**$B-1) # Each possible number
{for my $j(0..2**$B-1) # Each possible number
{my %a = $c->setBits('a', $i); # Number a
my %b = $c->setBits('b', $j); # Number b
my $s = $c->simulate({%a, %b}, $i==1&&$j==2?(svg=>"svg/CompareLt$B"):()); # Svg drawing of layout
is_deeply($s->value("out"), $i < $j ? 1 : 0); # More than
is_deeply($s->steps, 4); # Number of steps to stability
}
}
}
#latest:;
if (1) # Masked multiplexer: copy B bit word selected by mask from W possible locations
{my $B = 4; my $W = 4;
my $c = newChip;
for my $w(1..$W) # Input words
{$c->input("s$w"); # Selection mask
for my $b(1..$B) # Bits of input word
{$c->input("i$w$b");
$c->and( "s$w$b", ["i$w$b", "s$w"]);
}
}
for my $b(1..$B) # Or selected bits together to make output
{$c->or ("c$b", [map {"s$b$_"} 1..$W]); # Combine the selected bits to make a word
$c->output("o$b", "c$b"); # Output the word selected
}
my $s = $c->simulate(
{s1 =>0, s2 =>0, s3 =>1, s4 =>0,
i11=>0, i12=>0, i13=>0, i14=>1,
i21=>0, i22=>0, i23=>1, i24=>0,
i31=>0, i32=>1, i33=>0, i34=>0,
i41=>1, i42=>0, i43=>0, i44=>0});
is_deeply([@{$s->values}{qw(o1 o2 o3 o4)}], [qw(0 0 1 0)]); # Number selected by mask
is_deeply($s->steps, 3);
}
#latest:;
if (1) # Rename a gate
{my $i = newChip(name=>"inner");
$i->input ("i");
my $n = $i->not ("n", "i");
$i->output("io", "n");
my $ci = cloneGate $i, $n;
renameGate $i, $ci, "aaa";
is_deeply($ci->inputs, { n => "i" });
is_deeply($ci->output, "(aaa n)");
is_deeply($ci->io, 0);
}
#latest:;
if (1) #Tinstall #TconnectBits # Install one chip inside another chip, specifically one chip that performs NOT is installed once to flip a value
{my $i = newChip(name=>"not");
$i-> inputBits('i', 1);
$i-> notBits(qw(n i));
$i->outputBits(qw(o n));
my $o = newChip(name=>"outer");
$o->inputBits('i', 1); $o->outputBits(qw(n i));
$o->inputBits('I', 1); $o->outputBits(qw(N I));
my %i = connectBits($i, 'i', $o, 'n');
my %o = connectBits($i, 'o', $o, 'I');
$o->install($i, {%i}, {%o});
my %d = $o->setBits('i', 1);
my $s = $o->simulate({%d}, printOff=>"dump/not1", svg=>"svg/not1");
is_deeply($s->steps, 2);
is_deeply($s->values, {"(not 1 n_1)"=>0, "i_1"=>1, "N_1"=>0 });
}
#latest:;
if (1) #TconnectWords # Install one chip inside another chip, specifically one chip that performs NOT is installed three times sequentially to flip a value
{my $i = newChip(name=>"not");
$i-> inputWords('i', 1, 1);
$i-> notWords(qw(n i));
$i->outputWords(qw(o n));
my $o = newChip(name=>"outer");
$o->inputWords('i', 1, 1); $o->output(nn('n', 1, 1), nn('i', 1, 1));
$o->inputWords('I', 1, 1); $o->output(nn('N', 1, 1), nn('I', 1, 1));
my %i = connectWords($i, 'i', $o, 'n', 1, 1);
my %o = connectWords($i, 'o', $o, 'I', 1, 1);
$o->install($i, {%i}, {%o});
my %d = $o->setWords('i', 1);
my $s = $o->simulate({%d}, printOff=>"dump/not1", svg=>"svg/not1");
is_deeply($s->steps, 2);
is_deeply($s->values, { "(not 1 n_1_1)" => 0, "i_1_1" => 1, "N_1_1" => 0 });
}
#latest:;
if (1) #Tsimulate
{my $i = newChip(name=>"inner");
$i->input ("Ii");
$i->not ("In", "Ii");
$i->output( "Io", "In");
my $o = newChip(name=>"outer");
$o->input ("Oi1");
$o->output("Oo1", "Oi1");
$o->input ("Oi2");
$o->output("Oo2", "Oi2");
$o->input ("Oi3");
$o->output("Oo3", "Oi3");
$o->input ("Oi4");
$o->output("Oo", "Oi4");
$o->install($i, {Ii=>"Oo1"}, {Io=>"Oi2"});
$o->install($i, {Ii=>"Oo2"}, {Io=>"Oi3"});
$o->install($i, {Ii=>"Oo3"}, {Io=>"Oi4"});
my $s = $o->simulate({Oi1=>1}, printOff=>"dump/not3", svg=>"svg/not3");
is_deeply($s->value("Oo"), 0);
is_deeply($s->steps, 4);
}
#latest:;
if (1) #TpointMaskToInteger
{my $B = 4;
my $N = 2**$B-1;
my $c = Silicon::Chip::newChip(title=><<"END");
$B bits point mask to integer
END
$c->inputBits (qw( i), $N); # Mask with no more than one bit on
$c->pointMaskToInteger(qw(o i)); # Convert
$c->outputBits (qw(out o)); # Mask with no more than one bit on
for my $i(0..$N) # Each position of mask
{my %i = setBits($c, 'i', $i ? 1<<($i-1) : 0); # Point in each position with zero representing no position
my $s = $c->simulate(\%i, $i == 5 ? (svg=>"svg/point$B") : ());
is_deeply($s->steps, 2);
my %o = $s->values->%*; # Output bits
my $n = eval join '', '0b', map {$o{n(o,$_)}} reverse 1..$B; # Output bits as number
is_deeply($n, $i);
}
}
#latest:;
if (1) #TintegerToPointMask
{my $B = 3;
my $N = 2**$B-1;
my $c = Silicon::Chip::newChip(title=><<"END");
$B bit integer to $N bits monotone mask.
END
$c->inputBits (qw( i), $B); # Input bus
$c->integerToPointMask(qw(m i));
$c->outputBits (qw(o m));
for my $i(0..$N) # Each position of mask
{my %i = setBits($c, 'i', $i);
my $s = $c->simulate(\%i, $i == 5 ? (svg=>"svg/integerToMontoneMask$B"):());
is_deeply($s->steps, 3);
my $r = $s->bint('o'); # Mask values
is_deeply($r, $i ? 1<<($i-1) : 0); # Expected mask
}
}
#latest:;
if (1) #TmonotoneMaskToInteger
{my $B = 4;
my $N = 2**$B-1;
my $c = Silicon::Chip::newChip(title=><<"END");
$N bits monotone mask to $B bit integer
END
$c->inputBits ('i', $N);
$c->monotoneMaskToInteger(qw(m i));
$c->outputBits (qw(o m));
for my $i(0..$N-1) # Each monotone mask
{my %i = setBits($c, 'i', $i > 0 ? 1<<$i-1 : 0);
my $s = $c->simulate(\%i,
$i == 5 ? (svg=>"svg/monotoneMaskToInteger$B") : ());
is_deeply($s->steps, 4);
is_deeply($s->bint('m'), $i);
}
}
#latest:;
if (1) #TintegerToMonotoneMask
{my $B = 4;
my $N = 2**$B-1;
my $c = Silicon::Chip::newChip(title=><<"END");
Convert $B bit integer to $N bit monotone mask
END
$c->inputBits ('i', $B); # Input gates
$c->integerToMonotoneMask(qw(m i));
$c->outputBits (qw(o m)); # Output gates
for my $i(0..$N) # Each position of mask
{my %i = setBits($c, 'i', $i); # The number to convert
my $s = $c->simulate(\%i, $i == 2 ? (svg=>"svg/integerToMontoneMask$B"):());
is_deeply($s->steps, 4);
is_deeply($s->bint('o'), $i > 0 ? ((1<<$N)-1)>>($i-1)<<($i-1) : 0); # Expected mask
}
}
#latest:;
if (1) #TchooseWordUnderMask #TsetBits #TsetWords
{my $B = 3; my $W = 4;
my $c = Silicon::Chip::newChip(title=><<"END");
Choose one of $W words of $B bits
END
$c->inputWords ('w', $W, $B);
$c->inputBits ('m', $W);
$c->chooseWordUnderMask(qw(W w m));
$c->outputBits (qw(o W));
my %i = setWords($c, 'w', 0b000, 0b001, 0b010, 0b0100);
my %m = setBits ($c, 'm', 1<<2); # Choose the third word
my $s = $c->simulate({%i, %m}, svg=>"svg/choose_${W}_$B");
is_deeply($s->steps, 3);
is_deeply($s->bint('o'), 0b010);
}
#latest:;
if (1) #TfindWord
{my $B = 3; my $W = 2**$B-1;
my $c = Silicon::Chip::newChip(title=><<END);
Search $W words of $B bits
END
$c->inputBits ('k', $B); # Search key
$c->inputWords('w', 2**$B-1, $B); # Words to search
$c->findWord (qw(m k w)); # Find the word
$c->outputBits(qw(M m)); # Output mask
my %w = setWords($c, 'w', reverse 1..$W);
for my $k(0..$W) # Each possible key
{my %k = setBits($c, 'k', $k);
my $s = $c->simulate({%k, %w}, $k == 3 ? (svg=>q(svg/findWord)) : ());
is_deeply($s->steps, 3);
is_deeply($s->bint('M'),$k ? 2**($W-$k) : 0);
}
}
#latest:;
if (1) #TinputBits #ToutputBits #TnotBits #TSilicon::Chip::Simulation::bint
{my $W = 8;
my $i = newChip(name=>"not");
$i->inputBits('i', $W);
$i->notBits (qw(n i));
$i->outputBits(qw(o n));
my $o = newChip(name=>"outer");
$o->inputBits ('a', $W);
$o->outputBits(qw(A a));
$o->inputBits ('b', $W);
$o->outputBits(qw(B b));
my %i = connectBits($i, 'i', $o, 'A');
my %o = connectBits($i, 'o', $o, 'b');
$o->install($i, {%i}, {%o});
my %d = setBits($o, 'a', 0b10110);
my $s = $o->simulate({%d}, svg=>q(svg/not));
is_deeply($s->bint('B'), 0b11101001);
}
#latest:;
if (1) #TandBits #TorBits #TnandBits #TnorBits
{my $W = 8;
my $c = newChip();
$c-> inputBits('i', $W);
$c-> andBits(qw(and i));
$c-> orBits(qw(or i));
$c-> nandBits(qw(nand i));
$c-> norBits(qw(nor i));
$c->output (qw(And and));
$c->output (qw(Or or));
$c->output (qw(nAnd nand));
$c->output (qw(nOr nor));
my %d = setBits($c, 'i', 0b10110);
my $s = $c->simulate({%d}, svg=>q(svg/andOrBits));
is_deeply($s->value("And"), 0);
is_deeply($s->value("Or"), 1);
is_deeply($s->value("nAnd"), 1);
is_deeply($s->value("nOr"), 0);
}
#latest:;
if (1) #TandWords #TandWordsX #TorWords #TorWordsX #ToutputBits #TnotWords
{my @B = ((my $W = 4), (my $B = 2));
my $c = newChip();
$c->inputWords ('i', @B);
$c->andWords (qw(and i));
$c->andWordsX (qw(andX i));
$c-> orWords (qw( or i));
$c-> orWordsX (qw( orX i));
$c->notWords (qw(n i));
$c->outputBits (qw(And and));
$c->outputBits (qw(AndX andX));
$c->outputBits (qw(Or or));
$c->outputBits (qw(OrX orX));
$c->outputWords(qw(N n));
my %d = setWords($c, 'i', 0b00, 0b01, 0b10, 0b11);
my $s = $c->simulate({%d}, svg=>"svg/andOrWords$W");
is_deeply($s->bint('And'), 0b1000);
is_deeply($s->bint('AndX'), 0b0000);
is_deeply($s->bint('Or'), 0b1110);
is_deeply($s->bint('OrX'), 0b11);
is_deeply([$s->wordsToInteger('N')], [3, 2, 1, 0]);
}
#latest:;
if (1) #TandWords #TorWords #TSilicon::Chip::Simulation::wordXToInteger #TSilicon::Chip::Simulation::wordsToInteger #TinputWords #ToutputWords
{my @b = ((my $W = 4), (my $B = 3));
my $c = newChip();
$c->inputWords ('i', @b);
$c->outputWords(qw(o i));
my %d = setWords($c, 'i', 0b000, 0b001, 0b010, 0b011);
my $s = $c->simulate({%d}, svg=>"svg/words$W");
is_deeply([$s->wordsToInteger('o')], [0..3]);
is_deeply([$s->wordXToInteger('o')], [10, 12, 0]);
}
#latest:;
if (1) #TchooseFromTwoWords
{my $B = 4;
my $c = newChip();
$c->inputBits('a', $B); # First word
$c->inputBits('b', $B); # Second word
$c->input ('c'); # Chooser
$c->chooseFromTwoWords(qw(o a b c)); # Generate gates
$c->outputBits('out', 'o'); # Result
my %a = setBits($c, 'a', 0b0011);
my %b = setBits($c, 'b', 0b1100);
my $s = $c->simulate({%a, %b, c=>1}, svg=>q(svg/chooseFromTwoWords));
is_deeply($s->steps, 4);
is_deeply($s->bint('out'), 0b1100);
my $t = $c->simulate({%a, %b, c=>0});
is_deeply($t->steps, 4);
is_deeply($t->bint('out'), 0b0011);
}
#latest:;
if (1) #TenableWord
{my $B = 4;
my $c = newChip();
$c->inputBits ('a', $B); # Word
$c->input ('c'); # Choice bit
$c->enableWord(qw(o a c)); # Generate gates
$c->outputBits(qw(out o)); # Result
my %a = setBits($c, 'a', 3);
my $s = $c->simulate({%a, c=>1}, svg=>q(svg/enableWord));
is_deeply($s->steps, 4);
is_deeply($s->bint('out'), 3);
my $t = $c->simulate({%a, c=>0});
is_deeply($t->steps, 4);
is_deeply($t->bint('out'), 0);
}
#latest:;
if (1) #TmonotoneMaskToPointMask
{my $B = 4;
my $c = newChip();
$c->inputBits('m', $B); # Monotone mask
$c->monotoneMaskToPointMask(qw(o m)); # Generate gates
$c->outputBits('out', 'o'); # Point mask
for my $i(0..$B)
{my %m = $c->setBits('m', eval '0b'.(1 x $i).('0' x ($B-$i)));
my $s = $c->simulate({%m});
is_deeply($s->steps, 2);
is_deeply($s->bint('out'), $i ? (1<<($B-1)) / (1<<($i-1)) : 0);
}
}
#latest:;
if (1) # Internal input gate #TconnectInput #TSilicon::Chip::Simulation::value
{my $c = newChip();
$c->input ('i'); # Input
$c->input ('j'); # Internal input which we will connect to later
$c->output(qw(o j)); # Output
$c->connectInput(qw(j i));
my $s = $c->simulate({i=>1});
is_deeply($s->steps, 1);
is_deeply($s->value("j"), undef);
is_deeply($s->value("o"), 1);
}
#latest:;
if (1) #Twords # Internal input gate #TconnectInput #TSilicon::Chip::Simulation::value
{my @n = qw(3 2 1 2 3);
my $c = newChip();
$c->words('i', 2, @n); # Input
$c->outputWords(qw(o i)); # Output
my $s = $c->simulate({});
is_deeply($s->steps, 2);
is_deeply([$s->wordsToInteger("i")], [@n]);
}
#latest:;
if (1) #Twords # Internal input gate #TconnectInput #TSilicon::Chip::Simulation::value
{my @n = qw(3 2 1 2 3);
my $c = newChip();
$c->words('i', 2, @n); # Input
$c->outputWords(qw(o i)); # Output
my $s = $c->simulate({});
is_deeply($s->steps, 2);
is_deeply([$s->wordsToInteger("i")], [@n]);
}
#latest:;
if (1) #TsetSizeBits #TsetSizeWords
{my $c = newChip();
$c->setSizeBits ('i', 2);
$c->setSizeWords('j', 3, 2);
is_deeply($c->sizeBits, {i => 2, j_1 => 2, j_2 => 2, j_3 => 2});
is_deeply($c->sizeWords, {j => [3, 2]});
}
#done_testing();
finish: 1;