838 lines
16 KiB
Ruby
Executable File
838 lines
16 KiB
Ruby
Executable File
# Ruby Circuit Simulator
|
|
# Library file
|
|
|
|
# holds a digital value
|
|
# can call listener callbacks when value changes
|
|
|
|
require_relative "net"
|
|
#add some extra functionality to the basic NetPort
|
|
class Port < NetPort
|
|
include Comparable
|
|
|
|
#override to handle bitstrings
|
|
def value=(new_value)
|
|
if new_value.class == String
|
|
if new_value.length != self.width
|
|
raise ArgumentError, "Wrong width for bitstring: #{new_value}"
|
|
end
|
|
numval = 0
|
|
new_value.reverse.each do |bit|
|
|
numval = numval << 1
|
|
if bit == "1"
|
|
numval += 1
|
|
elsif bit != "0"
|
|
raise ArgumentError, "String values (#{new_value}) must be binary"
|
|
end
|
|
end
|
|
super(numval)
|
|
else
|
|
super(new_value)
|
|
end
|
|
end
|
|
|
|
def convert_port(otherval)
|
|
#checks if argument is a port or number
|
|
#if it is a number, convert it to a contant port
|
|
if otherval.class == Integer
|
|
return PortConstant.new(self.width, otherval)
|
|
else
|
|
return otherval
|
|
end
|
|
end
|
|
|
|
def &(other)
|
|
other = convert_port(other)
|
|
return AndGate.new(self,other).out
|
|
end
|
|
|
|
def |(other)
|
|
other = convert_port(other)
|
|
return OrGate.new(self,other).out
|
|
end
|
|
|
|
def ^(other)
|
|
other = convert_port(other)
|
|
return XorGate.new(self, other).out
|
|
end
|
|
|
|
def !
|
|
return NotGate.new(self).out
|
|
end
|
|
|
|
def +(other)
|
|
other = convert_port(other)
|
|
return Adder.new(self.width,{"a"=>self,"b"=>other}).out
|
|
end
|
|
|
|
def -(other)
|
|
other = convert_port(other)
|
|
return Subtractor.new(self.width,{"a"=>self,"b"=>other}).out
|
|
end
|
|
|
|
def slice(index)
|
|
if index.class==Integer
|
|
port=Port.new(1) #single line
|
|
mask = 1 << index
|
|
shift = index
|
|
else
|
|
port=Port.new(index.size) #range
|
|
mask = (2**(index.size) - 1) << index.first
|
|
shift = index.first
|
|
end
|
|
#copies slice to new port
|
|
self.add_callback do |new_value|
|
|
port.value = (new_value & mask) >> shift
|
|
end
|
|
return port
|
|
end
|
|
|
|
def join(other,last=false)
|
|
port=Port.new(self.width+other.width)
|
|
def update_port(last,port,other)
|
|
if self.is_defined? && other.is_defined?
|
|
if last
|
|
port.value=(other.value << self.width) + self.value
|
|
else
|
|
port.value=(self.value << other.width) + other.value
|
|
end
|
|
else
|
|
port.undefine
|
|
end
|
|
end
|
|
self.add_callback {|value| update_port(last,port,other)}
|
|
other.add_callback {|value| update_port(last,port,other)}
|
|
return port
|
|
end
|
|
|
|
def <=>(other)
|
|
other = convert_port(other)
|
|
return self.value<=>other.value
|
|
end
|
|
|
|
def >>(shiftbits)
|
|
return LSR.new(self.width, shiftbits).in(self).out
|
|
end
|
|
|
|
def <<(shiftbits)
|
|
return LSL.new(self.width, shiftbits).in(self).out
|
|
end
|
|
|
|
def bitstring
|
|
if is_defined?
|
|
mask = 1 << (self.width - 1)
|
|
strval = ""
|
|
self.width.times do
|
|
if (self.value & mask) > 0
|
|
strval += "1"
|
|
else
|
|
strval += "0"
|
|
end
|
|
mask = mask >> 1
|
|
end
|
|
strval
|
|
else
|
|
"X"*self.width
|
|
end
|
|
end
|
|
|
|
def method_missing(m, *args, &block)
|
|
return self
|
|
end
|
|
end
|
|
|
|
class PortConstant < Port
|
|
def initialize(width, value)
|
|
super(width)
|
|
@assigned = false
|
|
self.value = value
|
|
@assigned = true
|
|
end
|
|
|
|
def _update(newvalue)
|
|
if @assigned
|
|
#not initial assigment
|
|
#raise RuntimeError, "Cannot change value of constant port"
|
|
end
|
|
end
|
|
end
|
|
|
|
|
|
class Gate
|
|
def initialize(*args)
|
|
@inputs = []
|
|
if args.length==0
|
|
@width=1
|
|
@out = Port.new(@width)
|
|
elsif args[0].class==Integer
|
|
@width=args.shift
|
|
@out = Port.new(@width)
|
|
args.each do |input|
|
|
add_input(input)
|
|
end
|
|
else
|
|
@width=args[0].width
|
|
@out = Port.new(@width)
|
|
args.each do |input|
|
|
add_input(input)
|
|
end
|
|
end
|
|
end
|
|
|
|
def add_input(input_port)
|
|
if input_port.width != @width then
|
|
raise ArgumentError, "Incorrect port width"
|
|
end
|
|
@inputs.push(input_port)
|
|
input_port.add_callback {|value| self.input_changed(value)}
|
|
input_changed(input_port.value)
|
|
end
|
|
|
|
def out
|
|
return @out
|
|
end
|
|
|
|
end
|
|
|
|
|
|
class NotGate < Gate
|
|
def initialize(*args)
|
|
super
|
|
@outmask = (2**@width)-1
|
|
end
|
|
|
|
def add_input(input_port)
|
|
if @inputs.length > 0 then
|
|
raise ArgumentError, "Cannot add multiple inputs to NotGate"
|
|
end
|
|
super
|
|
end
|
|
|
|
alias set_input add_input
|
|
|
|
def input_changed(new_value)
|
|
inport=@inputs[0]
|
|
if inport.is_defined?
|
|
out.value = (~inport.value) & @outmask
|
|
else
|
|
out.undefine
|
|
end
|
|
end
|
|
|
|
def self.test
|
|
puts "NOT Test:"
|
|
nin = Port.new()
|
|
not_gate = NotGate.new(nin)
|
|
dbg = Dbg.new( {"in"=>nin, "out"=>not_gate.out})
|
|
dbg.out
|
|
nin.value = 0
|
|
dbg.out
|
|
nin.value = 1
|
|
dbg.out
|
|
end
|
|
end
|
|
|
|
class AndGate < Gate
|
|
|
|
def input_changed(new_value)
|
|
andval = nil
|
|
@inputs.each do |inport|
|
|
if inport.is_defined?
|
|
if andval == nil
|
|
andval = inport.value
|
|
else
|
|
andval = andval & inport.value
|
|
end
|
|
else
|
|
out.undefine
|
|
return
|
|
end
|
|
end
|
|
out.value = andval
|
|
end
|
|
|
|
def self.test()
|
|
puts "AND Test:"
|
|
in_a = Port.new()
|
|
in_b = Port.new()
|
|
and_gate = AndGate.new(in_a, in_b)
|
|
dbg = Dbg.new( {"a"=>in_a, "b"=>in_b, "out"=>and_gate.out})
|
|
dbg.out
|
|
in_a.value=0
|
|
in_b.value=0
|
|
dbg.out
|
|
in_a.value=1
|
|
in_b.value=0
|
|
dbg.out
|
|
in_a.value=0
|
|
in_b.value=1
|
|
dbg.out
|
|
in_a.value=1
|
|
in_b.value=1
|
|
dbg.out
|
|
end
|
|
end
|
|
|
|
class OrGate < Gate
|
|
|
|
def input_changed(new_value)
|
|
orval = nil
|
|
@inputs.each do |inport|
|
|
if inport.is_defined?
|
|
if orval == nil
|
|
orval = inport.value
|
|
else
|
|
orval = orval | inport.value
|
|
end
|
|
else
|
|
out.undefine
|
|
return
|
|
end
|
|
end
|
|
out.value = orval
|
|
end
|
|
|
|
def self.test()
|
|
puts "OR Test:"
|
|
in_a = Port.new()
|
|
in_b = Port.new()
|
|
or_gate = OrGate.new(in_a, in_b)
|
|
dbg = Dbg.new( {"a"=>in_a, "b"=>in_b, "out"=>or_gate.out})
|
|
dbg.out
|
|
in_a.value=0
|
|
in_b.value=0
|
|
dbg.out
|
|
in_a.value=1
|
|
in_b.value=0
|
|
dbg.out
|
|
in_a.value=0
|
|
in_b.value=1
|
|
dbg.out
|
|
in_a.value=1
|
|
in_b.value=1
|
|
dbg.out
|
|
end
|
|
end
|
|
|
|
|
|
class XorGate < Gate
|
|
|
|
def input_changed(new_value)
|
|
xorval = nil
|
|
@inputs.each do |inport|
|
|
if inport.is_defined?
|
|
if xorval == nil
|
|
xorval = inport.value
|
|
else
|
|
xorval = xorval ^ inport.value
|
|
end
|
|
else
|
|
out.undefine
|
|
return
|
|
end
|
|
end
|
|
out.value = xorval
|
|
end
|
|
|
|
def self.test()
|
|
puts "XOR Test:"
|
|
in_a = Port.new()
|
|
in_b = Port.new()
|
|
or_gate = OrGate.new(in_a, in_b)
|
|
dbg = Dbg.new( {"a"=>in_a, "b"=>in_b, "out"=>or_gate.out})
|
|
dbg.out
|
|
in_a.value=0
|
|
in_b.value=0
|
|
dbg.out
|
|
in_a.value=1
|
|
in_b.value=0
|
|
dbg.out
|
|
in_a.value=0
|
|
in_b.value=1
|
|
dbg.out
|
|
in_a.value=1
|
|
in_b.value=1
|
|
dbg.out
|
|
end
|
|
end
|
|
|
|
|
|
class Device
|
|
def define_port(name, width=1, &callback)
|
|
#create a method with the same name as the input
|
|
#to assign a port to the input
|
|
var_name = "@" + name
|
|
port = Port.new(width)
|
|
port.set_name(name)
|
|
port.set_parent(self)
|
|
instance_variable_set(var_name, port)
|
|
define_singleton_method(name) do |other=nil|
|
|
if other.class == NilClass #avoids overloaded compare for Port
|
|
#getter
|
|
instance_variable_get(var_name)
|
|
else
|
|
#connect to given port
|
|
if other.class == Integer
|
|
#convert constant
|
|
other = PortConstant.new(width, other)
|
|
end
|
|
instance_variable_get(var_name).connect(other)
|
|
self #for chained init calls
|
|
end
|
|
end
|
|
if block_given?
|
|
port.add_callback(&callback)
|
|
end
|
|
port
|
|
end
|
|
|
|
def define_input(name, width=1)
|
|
#automatically connects to on_change method
|
|
define_port(name, width) { |new_value| on_change(new_value) }
|
|
end
|
|
|
|
#for contained subdevices
|
|
def define_device(name, device)
|
|
var_name = "@" + name
|
|
instance_variable_set(var_name, device)
|
|
define_singleton_method(name) { instance_variable_get(var_name) }
|
|
device.set_parent(self)
|
|
device.set_name(name)
|
|
end
|
|
|
|
#call on the hash of arguments at init
|
|
def init_assign(hash)
|
|
hash.each do |name, port|
|
|
#check if there is a defined method (port) that matches
|
|
if self.respond_to?(name)
|
|
method(name).call(port)
|
|
else
|
|
raise ArgumentError, "No defined input '#{name}'"
|
|
end
|
|
end
|
|
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
|
|
|
|
#logical shift right
|
|
class LSR < Device
|
|
def initialize(width, bitshift, init_args={})
|
|
define_input("in", width)
|
|
define_port("out", width)
|
|
@bitshift = bitshift
|
|
init_assign(init_args)
|
|
end
|
|
|
|
def on_change(data_val)
|
|
if @in.is_defined?
|
|
out.value = @in.value >> @bitshift
|
|
else
|
|
out.undefine
|
|
end
|
|
end
|
|
end
|
|
|
|
#logical shift left
|
|
class LSL < Device
|
|
def initialize(width, bitshift, init_args={})
|
|
define_input("in", width)
|
|
define_port("out", width)
|
|
@bitshift = bitshift
|
|
@outmask = (2**width) - 1
|
|
init_assign(init_args)
|
|
end
|
|
|
|
def on_change(data_val)
|
|
if @in.is_defined?
|
|
out.value = (@in.value << @bitshift) & @outmask
|
|
else
|
|
out.undefine
|
|
end
|
|
end
|
|
end
|
|
|
|
|
|
class Reg < Device
|
|
def initialize(width, init_args={})
|
|
define_input("in", width)
|
|
define_port("out", width)
|
|
define_input("clk")
|
|
define_input("en")
|
|
define_input("rst")
|
|
init_assign(init_args)
|
|
end
|
|
|
|
def on_change(data_val)
|
|
if rst.value == 1
|
|
out.value = 0
|
|
elsif en.value == 1 && clk.posedge?
|
|
out.value = @in.value
|
|
end
|
|
end
|
|
end
|
|
|
|
class Counter < Device
|
|
def initialize(width, init_args={})
|
|
define_input("in", width)
|
|
define_port("out", width)
|
|
define_input("clk")
|
|
define_input("load")
|
|
define_input("rst")
|
|
init_assign(init_args)
|
|
end
|
|
|
|
def on_change(data_val)
|
|
#puts "on_change, clk=#{clk.value}, rst=#{rst.value}"
|
|
if out.undefined?
|
|
out.value=0
|
|
end
|
|
if rst.value == 1
|
|
#puts "resetting"
|
|
out.value = 0
|
|
elsif clk.posedge?
|
|
#puts "posedge"
|
|
if load.value == 1
|
|
out.value = @in.value
|
|
else
|
|
#puts "no load, old out=#{out.value}"
|
|
out.value = out.value + 1
|
|
#puts "new out=#{out.value}"
|
|
end
|
|
end
|
|
end
|
|
|
|
def self.test
|
|
puts "Reg Test:"
|
|
din = Port.new(8)
|
|
c = Port.new
|
|
e = Port.new
|
|
r = Port.new
|
|
reg = Reg.new(8,{"in"=>din,"clk"=>c,"en"=>e,"rst"=>r})
|
|
dbg = Dbg.new({"in"=>din, "clk"=>c, "en"=>e, "rst"=>r, "out"=>reg.out})
|
|
r.value = 1
|
|
c.value = 0
|
|
e.value = 0
|
|
din.value = 23
|
|
dbg.out
|
|
r.value = 1
|
|
dbg.out
|
|
r.value = 0
|
|
dbg.out
|
|
c.value = 1
|
|
dbg.out
|
|
e.value = 1
|
|
dbg.out
|
|
c.value = 0
|
|
dbg.out
|
|
c.value = 1
|
|
dbg.out
|
|
din.value = 37
|
|
dbg.out
|
|
end
|
|
|
|
end
|
|
|
|
class Mux < Device
|
|
def initialize(data_width=1, select_width=1, init_args={})
|
|
num_inputs = 2**select_width
|
|
i=0
|
|
@inputs = []
|
|
num_inputs.times do
|
|
@inputs << define_input("in"+i.to_s, data_width)
|
|
i += 1
|
|
end
|
|
define_input("sel", select_width)
|
|
define_port("out", data_width)
|
|
init_assign(init_args)
|
|
end
|
|
|
|
def on_change(new_value)
|
|
if sel.is_defined?
|
|
|
|
out.value = @inputs[sel.value].value
|
|
else
|
|
out.undefine
|
|
end
|
|
end
|
|
|
|
def self.test
|
|
puts "Mux test:"
|
|
select = Port.new(1)
|
|
a = Port.new(4)
|
|
b = Port.new(4)
|
|
mux = Mux.new(4, 1).sel(select).in0(a).in1(b)
|
|
dbg = Dbg.new({"sel"=>select, "0"=>a, "1"=>b, "out"=>mux.out})
|
|
a.value = 3
|
|
b.value = 14
|
|
dbg.out
|
|
select.value = 0
|
|
dbg.out
|
|
select.value = 1
|
|
dbg.out
|
|
end
|
|
end
|
|
|
|
|
|
class Decoder < Device
|
|
def initialize(width, init_args={})
|
|
@width=width
|
|
define_input("sel", width)
|
|
define_input("en")
|
|
num_of_outputs=2**width
|
|
i=0
|
|
@outputs = []
|
|
num_of_outputs.times do
|
|
#create an ordered list of all the outputs
|
|
@outputs << define_port("o"+i.to_s)
|
|
i+=1
|
|
end
|
|
init_assign(init_args)
|
|
end
|
|
|
|
def on_change(data_val)
|
|
if en
|
|
if sel.is_defined?
|
|
channel=sel.value
|
|
i=0
|
|
(@width**2).times do
|
|
if i==channel
|
|
@outputs[i].value=1
|
|
else
|
|
@outputs[i].value=0
|
|
end
|
|
i+=1
|
|
end
|
|
else
|
|
i=0
|
|
(@width**2).times do
|
|
@outputs[i].undefine
|
|
i+=1
|
|
end
|
|
end
|
|
else
|
|
i=0
|
|
(@width**2).times do
|
|
@outputs[i].value=0
|
|
i+=1
|
|
end
|
|
end
|
|
end
|
|
|
|
def self.test
|
|
puts "Decoder Test:"
|
|
select=Port.new(2)
|
|
decoder=Decoder.new(2,"sel"=>select)
|
|
dbg = Dbg.new({"sel"=>select, "0"=>decoder.o0, "1"=>decoder.o1, "2"=>decoder.o2, "3"=>decoder.o3})
|
|
dbg.out
|
|
select.value=0
|
|
dbg.out
|
|
select.value=1
|
|
dbg.out
|
|
select.value=2
|
|
dbg.out
|
|
select.value=3
|
|
dbg.out
|
|
end
|
|
end
|
|
|
|
class PEncoder < Device
|
|
def initialize(width, init_args={})
|
|
define_port("out", width)
|
|
@inputs = []
|
|
@num_inputs = width**2
|
|
(0...@num_inputs).each do |i|
|
|
@inputs << define_input("in#{i}", 1)
|
|
end
|
|
init_assign(init_args)
|
|
end
|
|
|
|
def on_change(data_val)
|
|
(0...@num_inputs).each do |i|
|
|
if @inputs[i].is_defined?
|
|
if @inputs[i].value == 1
|
|
out.value = i
|
|
return
|
|
end
|
|
else
|
|
out.undefine
|
|
return
|
|
end
|
|
end
|
|
out.value = 0 #default, no active inputs
|
|
end
|
|
|
|
def self.test
|
|
puts "PEncoder test:"
|
|
in0 = Port.new
|
|
in1 = Port.new
|
|
in2 = Port.new
|
|
in3 = Port.new
|
|
out = Port.new(2)
|
|
dut = PEncoder.new(2).in0(in0)
|
|
.in1(in1)
|
|
.in2(in2)
|
|
.in3(in3)
|
|
.out(out)
|
|
dbg = Dbg.new({"0"=>dut.in0, "1"=>dut.in1, "2"=>dut.in2, "3"=>dut.in3, "Out"=>dut.out})
|
|
dbg.out
|
|
in0.value = 0
|
|
in1.value = 0
|
|
in2.value = 0
|
|
in3.value = 0
|
|
dbg.out
|
|
in2.value = 1
|
|
dbg.out
|
|
in1.value = 1
|
|
dbg.out
|
|
in3.value = 1
|
|
dbg.out
|
|
end
|
|
end
|
|
|
|
class Adder < Device
|
|
def initialize(width, init_args)
|
|
define_input("a", width)
|
|
define_input("b", width)
|
|
define_port("out", width)
|
|
init_assign(init_args)
|
|
@mask = 2**width - 1
|
|
end
|
|
|
|
def on_change(data_val)
|
|
if a.is_defined? && b.is_defined?
|
|
out.value = (a.value + b.value) & @mask
|
|
else
|
|
out.undefine
|
|
end
|
|
end
|
|
end
|
|
|
|
class Subtractor < Device
|
|
def initialize(width, init_args)
|
|
define_input("a", width)
|
|
define_input("b", width)
|
|
define_port("out", width)
|
|
init_assign(init_args)
|
|
@mask = 2**width - 1
|
|
end
|
|
|
|
def on_change(data_val)
|
|
if a.is_defined? && b.is_defined?
|
|
out.value = (a.value - b.value) & @mask
|
|
else
|
|
out.undefine
|
|
end
|
|
end
|
|
end
|
|
|
|
class Ram < Device
|
|
def initialize(data_width, addr_width, init_args={})
|
|
@mem=[]
|
|
define_input("in", data_width)
|
|
define_port("out", data_width)
|
|
define_input("addr", addr_width)
|
|
define_input("clk")
|
|
define_input("wr")
|
|
init_assign(init_args)
|
|
end
|
|
|
|
def set_data(init_array)
|
|
@mem = init_array
|
|
end
|
|
|
|
def on_change(data_val)
|
|
if wr.value == 1 && clk.posedge?
|
|
@mem[addr.value]=@in.value
|
|
out.undefine
|
|
elsif addr.is_defined? && @mem[addr.value] != nil
|
|
out.value=@mem[addr.value]
|
|
else
|
|
out.value=0
|
|
end
|
|
end
|
|
|
|
def [](addr)
|
|
return @mem[addr]
|
|
end
|
|
def []=(addr,value)
|
|
if value.class==String
|
|
@mem[addr]=value.to_i(2)
|
|
else
|
|
@mem[addr]=value
|
|
end
|
|
end
|
|
def self.test
|
|
puts "RAM test:"
|
|
din=Port.new(8)
|
|
addr=Port.new(8)
|
|
wr=Port.new
|
|
clk=Port.new
|
|
ram=Ram.new(8,8,{"in"=>din,"addr"=>addr,"wr"=>wr,"clk"=>clk})
|
|
dbg=Dbg.new({"in"=>din,"out"=>ram.out,"addr"=>addr,"wr"=>wr,"clk"=>clk})
|
|
clk.value=0
|
|
din.value=11
|
|
addr.value=0
|
|
wr.value=1
|
|
clk.value=1
|
|
dbg.out
|
|
clk.value=0
|
|
end
|
|
end
|
|
|
|
class Dbg
|
|
def initialize(ports,show_num=false)
|
|
@names = []
|
|
@ports = {}
|
|
@show_num=show_num
|
|
ports.each do |name,port|
|
|
@names.push(name)
|
|
@ports[name] = port
|
|
end
|
|
end
|
|
|
|
def add_trigger(port_name)
|
|
@ports[port_name].add_late_callback { |value| self.out }
|
|
end
|
|
|
|
def out
|
|
watch_str = ""
|
|
if @show_num
|
|
@names.each { |name| watch_str += "#{name}=#{@ports[name].value} " }
|
|
else
|
|
@names.each { |name| watch_str += "#{name}=#{@ports[name].bitstring} " }
|
|
end
|
|
puts watch_str
|
|
end
|
|
end
|
|
|
|
def test()
|
|
classes = []
|
|
ObjectSpace.each_object { |o| classes.push o if o.class == Class }
|
|
classes.each do |c|
|
|
if c!=Object && c.methods.include?(:test)
|
|
c.test
|
|
end
|
|
end
|
|
end
|
|
|
|
# PEncoder.test
|
|
test()
|