rcircuit/old/net.rb
2018-07-08 14:09:22 -05:00

224 lines
4.8 KiB
Ruby
Executable File

require 'set'
#endpoints for nets
class NetPort
def initialize(width=1)
@callbacks = []
@updating = false
#set up a new net with a matching width
Net.new(self, width)
end
def connect(other)
if other.is_defined?
self.value = other.value #drive into the new net first
end
Net.get_net(self).connect(other)
self #return self for call chaining
end
def value
Net.get_net(self).value
end
def bvalue
unless Net.get_net(self).value==nil
Net.get_net(self).value.to_s(2)
end
end
def width
Net.get_net(self).width
end
def value=(new_value)
Net.get_net(self).drive(new_value)
end
def posedge?
Net.get_net(self).posedge?
end
def negedge?
Net.get_net(self).negedge?
end
def is_defined?
self.value != nil
end
def undefine
self.value = nil
end
#called by Net, do not call directly
def _update(value)
if @updating
raise RuntimeError, "Signal loop detected in #{self.get_name}"
end
@updating = true
#send new value to each listener
@callbacks.each do |callback|
callback.call(value)
end
@updating = false
end
def add_callback(&callback)
#add block to the list, put at head
@callbacks.insert(0, callback)
self #return self for call chaining
end
def add_late_callback(&callback)
#add block to the list, put at end to be called last
@callbacks.push(callback)
self #return self for call chaining
end
def set_name(name)
@name = name
end
def set_parent(parent)
@parent = parent
end
def get_name
if @name == nil
@name = self.class
end
if @parent != nil
@parent.get_name + "." + @name
else
@name
end
end
end
class Net
@@assignments = {} #table of nets assigned to ports
@@propagate_list = [] #nets that have changed during propagate call
@@propagate_flag = false
def initialize(port, width=1)
if @@assignments.has_key?(port)
raise ArgumentError, "Repeat assignment of port to new net"
end
@ports = Set.new
@ports.add(port)
@width = width
@max_value = 2**@width - 1
@value = nil
@posedge = false
@negedge = false
@@assignments[port] = self
end
def ports
@ports
end
def width
@width
end
def connect(other)
if @width != other.width
raise ArgumentError, "Cannot connect nets of different widths"
end
if @@assignments.has_key?(other)
#net already assigned to other port.
#Merge them and update @@assignments table
other_net = @@assignments[other]
@ports.merge(other_net.ports)
other_net.ports.each { |port_in_other| @@assignments[port_in_other] = self }
else
#unassigned port, just add it
@ports.add(other)
@@assignments[other] = self
end
end
def drive(new_value)
if new_value.is_a?(NetPort)
new_value=new_value.value
end
if new_value != nil && (new_value < 0 || new_value > @max_value)
raise ArgumentError, "Invalid value (#{new_value}) for net"
end
if new_value != @value
@new_value = new_value
@@propagate_list.push(self) #schedule to run update
if not @@propagate_flag
#automatically propagate the change and all changes that result
@@propagate_flag = true #prevent recursion
Net.propagate
@@propagate_flag = false #unlock for next time
end
end
end
#loops until all updates complete
def self.propagate
#propagate list will grow as downstream nets are changed
#keep updating until none are left
while @@propagate_list.length > 0 do
net = @@propagate_list.shift
net.update
end
end
def update
@posedge = (@value == 0 && @new_value != nil && @new_value > 0)
@negedge = (@value != nil && @value > 0 && @new_value == 0)
@value = @new_value
@ports.each { |driven| driven._update(@new_value) }
#edges only last for the current update
@posedge = false
@negedge = false
end
def value
@value
end
def posedge?
@posedge
end
def negedge?
@negedge
end
def value=(new_value)
drive(new_value)
end
#get net currently assigned to the given port (or a new one)
def self.get_net(port)
if @@assignments.has_key?(port)
@@assignments[port]
else
self.new(port)
end
end
def self.test
a = NetPort.new(4).add_callback { |value| puts "A set to #{value}" }
b = NetPort.new(4).add_callback { |value| puts "B set to #{value}" }
c = NetPort.new(4).add_callback { |value| puts "C set to #{value}" }
d = NetPort.new(4).add_callback { |value| puts "D set to #{value}" }
#connect A-B and C-D
a.connect(b)
c.connect(d)
a.value = 1
c.value = 2
#connect them all together
b.connect(c)
a.value = 3
d.value = 4
end
end