224 lines
4.8 KiB
Ruby
Executable File
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
|