diff --git a/gate.rb b/gate.rb index bc0d23d..5eeb3c0 100644 --- a/gate.rb +++ b/gate.rb @@ -1,6 +1,47 @@ require_relative "port.rb" -# @abstract Subclass and override {#input_changed} to implement a gate +# @abstract Subclass and override {#inputs_changed} to implement a gate ## Base class for gates class Gate + + # @!attribute [r] out + # @return [Port] the output port of the gate + attr_reader :out + + # @param args [Array] These are the ports to be added to the gate. + # @note The first argument determines the width of the gate. + def initialize(*args) + @inputs=[] + @width=args[0].width + @out=Port.new(@width) + args.each do |input| + add_input(input) + end + end + + # Add a port to the gate. + # @param port [Port] The port to be added. It must match the width of the gate. + # @return [void] + def add_input(port) + if port.width != @width then + raise ArgumentError, "Incorrect width #{port.width}, expected #{@width}" + end + @inputs.push(port) + port.add_callback do |value| + vals=[] + @inputs.each do |port| + vals.push port.val + end + inputs_changed(vals) + end + vals=[] + @inputs.each do |port| + vals.push port.val + end + inputs_changed(vals) + end + + # @abstract Override this to implement a gate. + # @param vals [Array] List of values for connected ports. + def inputs_changed(vals); raise NotImplementedError; end end diff --git a/port.rb b/port.rb index 5581300..238dc8d 100644 --- a/port.rb +++ b/port.rb @@ -19,6 +19,7 @@ class Port # @param name [String] The name of the port def initialize(width, name="") @connected=[] + @callbacks=[] @propagating=false @val=0 @width=width @@ -26,8 +27,9 @@ class Port @strname=name end - # Sets the port's value - # @param val [Integer] The new value for the port + # Sets the port's value and calls all registered callbacks + # @param val [Integer] The new value for the port. + # It must not exceed (width^2)-1,or an ArgumentError will be raised. # @return [void] def setval(val) # Prevent infinite loops when the connected port calls back when propagating. @@ -37,6 +39,9 @@ class Port else raise ArgumentError,"#{val} is over maximum of #{@maxval}" end + @callbacks.each do |callback| + callback.call(@val) + end @propagating=true propagate() @propagating=false @@ -51,6 +56,14 @@ class Port port.connect_back(self) end + # Adds a callback. + # @yield When callback called, gives the value of the port. + # @yieldparam value [Integer] The value of the port. + def add_callback(&callback) + #add block to the list, put at head + @callbacks.insert(0, callback) + end + # Returns a string representation for debugging # @return [String] String name of the port, or super if none def to_s() diff --git a/spec/gate_spec.rb b/spec/gate_spec.rb new file mode 100644 index 0000000..1f11e22 --- /dev/null +++ b/spec/gate_spec.rb @@ -0,0 +1,34 @@ +require_relative "../gate.rb" +require_relative "../port.rb" + +describe Gate do + let(:klass) do + Class.new(Gate) do + def inputs_changed(vals) + tot=0 + vals.each do |val| + tot+=val + end + out.setval(tot) + end + end + end + it "calls #inputs_changed when input values change or an input is added" do + a=Port.new(8) + b=Port.new(8) + gate=klass.new(a,b) + expect(gate).to receive(:inputs_changed).with([8,0]) + a.setval(8) + expect(gate).to receive(:inputs_changed).with([8,10]) + b.setval(10) + end + + it "can perform computations in #inputs_changed" do + a=Port.new(8) + b=Port.new(8) + gate=klass.new(a,b) + a.setval(8) + b.setval(10) + expect(gate.out.val).to eq(18) + end +end diff --git a/spec/port_spec.rb b/spec/port_spec.rb index 35789ea..9c6b977 100644 --- a/spec/port_spec.rb +++ b/spec/port_spec.rb @@ -1,13 +1,13 @@ require_relative "../port.rb" describe Port do - it "should set the port's value when we call setval" do + it "should set the port's value when we call #setval" do a=Port.new(4) a.setval(1) expect(a).to eq 1 end - it "should propagate values when we call setval" do + it "should propagate values when we call #setval" do a=Port.new(4) b=Port.new(4) c=Port.new(4) @@ -42,4 +42,10 @@ describe Port do a=Port.new(4) expect {a.setval(16)}.to raise_error ArgumentError end + + it "should call registered callbacks" do + a=Port.new(4) + expect { |b| a.add_callback(&b); a.setval(10) }.to yield_with_args(10) + expect { |b| a.add_callback(&b); a.setval(8) }.to yield_with_args(8) + end end