module Origen
module Registers
# This is a regular Ruby array that is used to store collections of Bit objects, it has additional
# methods added to allow interaction with the contained bits.
# All Ruby array methods are also available - http://www.ruby-doc.org/core/classes/Array.html
#
# A BitCollection is returned whenever a subset of bits is requested from a register. Also whenever
# any of these methods are called on a register object a BitCollection is created on the fly that
# contains all bits in the register. This means that when interacting with a Register, a single Bit,
# or a group of Bit objects, the same API can be used as described below.
class BitCollection < Array
include Origen::SubBlocks::Path
DONT_CARE_CHAR = 'X'
OVERLAY_CHAR = 'V'
STORE_CHAR = 'S'
attr_accessor :name
def initialize(reg, name, data = []) # :nodoc:
if reg.respond_to?(:has_bits_enabled_by_feature?) && reg.has_parameter_bound_bits?
reg.update_bound_bits unless reg.updating_bound_bits?
end
@reg = reg
@name = name
[data].flatten.each { |item| self << item }
end
def bind(live_parameter)
parent.bind(name, live_parameter)
end
def [](*indexes)
return self if indexes.empty?
b = BitCollection.new(parent, name)
expand_and_order(*indexes).each do |i|
b << fetch(i)
end
# When 1 bit requested just return that bit, this is consistent with the original
# behaviour before sub collections were added
if b.size == 1
b.first
else
b
end
end
alias_method :bits, :[]
alias_method :bit, :[]
def parent
@reg
end
def path_var
if first.path_var
if first.path_var =~ /^\./
base = parent.path(relative_to: parent.parent)
"#{base}#{first.path_var}"
else
first.path_var
end
else
base = parent.path(relative_to: parent.parent)
if size == 1
"#{base}[#{position}]"
else
"#{base}[#{position + size - 1}:#{position}]"
end
end
end
def abs_path
first.abs_path
end
Bit::ACCESS_CODES.each do |code, _meta|
define_method "#{code}?" do
all? { |b| b.undefined? || b.send("#{code}?") }
end
end
# Copies all data and flags from one bit collection (or reg) object to another
#
# This method will accept a dumb value as the argument, in which case it is essentially a write,
# however it will also clear all flags.
def copy_all(reg)
if reg.respond_to?(:contains_bits?) && reg.contains_bits?
unless reg.size == size
puts 'Bit collection copy must be performed on collections of the same size.'
puts 'You can fix this by calling copy on a subset of the bits you require, e.g.'
puts ' larger_bit_collection[3..0].copy_all(smaller_bit_collection)'
puts
fail 'Mismatched size for bit collection copy'
end
size.times do |i|
source_bit = reg.bit[i]
if source_bit
self[i].overlay(source_bit.overlay_str) if source_bit.has_overlay?
self[i].write(source_bit.data)
self[i].read if source_bit.is_to_be_read?
self[i].store if source_bit.is_to_be_stored?
end
end
else
write(reg)
clear_flags
end
self
end
# Returns the access attribute of the first contained bit, in most normal use cases
# the application will naturally guarantee that when this is called all of the bits
# in the collection have the same access value.
#
# If you are worried about hitting the case where some bits have different values then
# use access!, but this will be a bit less efficient
def access(value = nil)
if value.nil?
first.access
else # set access
each { |b| b.set_access(value) }
self
end
end
# Like access but will raise an error if not all bits in the collection have the same
# access value
def access!
val = access
if any? { |b| b.access != val }
fail 'Not all bits the collection have the same access value!'
end
val
end
# Returns the description of the given bit(s) if any, if none then an empty array
# will be returned
#
# **Note** Adding a description field will override any comment-driven documentation
# of a bit collection (ie markdown style comments)
def description(bitname = nil, options = {})
bitname, options = nil, bitname if bitname.is_a?(Hash)
if name == :unknown
[]
else
@reg.description(name, options)
end
end
def full_name(bitname = nil, options = {})
bitname, options = nil, bitname if bitname.is_a?(Hash)
unless name == :unknown
@reg.full_name(name, options)
end
end
def bit_value_descriptions(_bitname = nil)
if name == :unknown
[]
else
@reg.bit_value_descriptions(name)
end
end
# Returns a dummy bit collection that is populated with un-writable bits that will
# read back as 0. This can be useful for padding out spaces in registers with something that
# responds like conventional bits.
def self.dummy(reg, name = nil, options = {})
name, options = nil, name if name.is_a?(Hash)
options = {
size: 8,
pos: 0
}.merge(options)
collection = new(reg, name)
pos = options[:pos]
options[:size].times do
bit = Bit.new(reg, pos, writable: false, feature: :dummy_feature)
collection << bit
pos += 1
end
collection
end
def contains_bits?
true
end
def inspect
"<#{self.class}:#{object_id}>"
end
# Returns the LSB position of the collection
def position
first.position
end
# Returns the data value held by the collection
# ==== Example
# reg(:control).write(0x55)
# reg(:control).data # => 0x55, assuming the reg has the required bits to store that
def data
data = 0
each_with_index { |bit, i| data |= bit.data << i }
data
end
alias_method :val, :data
alias_method :value, :data
# Returns the inverse of the data value held by the collection
def data_b
# (& operation takes care of Bignum formatting issues)
~data & ((1 << size) - 1)
end
# Supports reg.bit[0] and bitcollection.bit[0]
def bit
self
end
# Set the data value of the collection within the patgen, but not on silicon - i.e. calling
# write will not trigger a pattern write event.
def write(value, options = {})
# If an array is written it means a data value and an overlay have been supplied
# in one go...
if value.is_a?(Array) && !value.is_a?(BitCollection)
overlay(value[1])
value = value[0]
end
value = value.data if value.respond_to?('data')
each_with_index { |bit, i| bit.write(value[i], options) }
self
end
alias_method :data=, :write
alias_method :value=, :write
alias_method :val=, :write
# Will tag all bits for read and if a data value is supplied it
# will update the expected data for when the read is performed.
def read(value = nil, options = {}) # :nodoc:
# First properly assign the args if value is absent...
if value.is_a?(Hash)
options = value
value = nil
end
if value
value = Reg.clean_value(value)
write(value)
end
if options[:mask]
each_with_index { |bit, i| bit.read if options[:mask][i] == 1 }
each_with_index { |bit, i| bit.clear_read_flag if options[:mask][i] == 0 }
else
each(&:read)
end
self
end
# Returns a value representing the bit collection / register where a bit value of
# 1 means the bit is enabled for the given operation.
def enable_mask(operation)
str = ''
shift_out_left do |bit|
if operation == :store && bit.is_to_be_stored? ||
operation == :read && bit.is_to_be_read? ||
operation == :overlay && bit.has_overlay?
str += '1'
else
str += '0'
end
end
str.to_i(2)
end
# Attaches the supplied overlay string to all bits
# ==== Example
# reg(:data).overlay("data_val")
def overlay(value)
each { |bit| bit.overlay(value) }
self
end
# Resets all bits, this clears all flags and assigns the data value
# back to the reset state
def reset
each(&:reset)
self
end
# Shifts out a stream of bit objects corresponding to the size of the BitCollection. i.e. calling
# this on a 16-bit register this will pass back 16 bit objects.
# If there are holes in the given register then a dummy bit object will be returned that
# is not writable and which will always read as 0.
# ==== Example
# reg(:data).shift_out_left do |bit|
# bist_shift(bit)
# end
def shift_out_left
reverse_each { |bit| yield bit }
end
# Same as Reg#shift_out_left but starts from the MSB
def shift_out_right
each { |bit| yield bit }
end
# Returns true if any bits have the read flag set - see Bit#is_to_be_read?
# for more details.
def is_to_be_read?
any?(&:is_to_be_read?)
end
# Returns true if any bits have the store flag set - see Bit#is_to_be_stored?
# for more details.
def is_to_be_stored?
any?(&:is_to_be_stored?)
end
# Returns true if any bits have the update_required flag set - see Bit#update_required?
# for more details.
def update_required?
any?(&:update_required?)
end
# Calls the clear_flags method on all bits, see Bit#clear_flags for more details
def clear_flags
each(&:clear_flags)
self
end
# Returns the value you would need to write to the register to put the given
# value in these bits
def setting(value)
result = 0
each_with_index do |bit, i|
result |= bit.setting(value[i])
end
result
end
# Returns true if any bits within are tagged for overlay, supply a specific name
# to require a specific overlay only
# ==== Example
# myreg.overlay("data")
# myreg.has_overlay? # => true
# myreg.has_overlay?("address") # => false
# myreg.has_overlay?("data") # => true
def has_overlay?(name = nil)
any? { |bit| bit.has_overlay?(name) }
end
# Cycles through all bits and returns the last overlay value found, it is assumed therefore
# that all bits have the same overlay value when calling this method
# ==== Example
# myreg.overlay("data")
#
# myreg.overlay_str # => "data"
def overlay_str
result = ''
each do |bit|
result = bit.overlay_str if bit.has_overlay?
end
result.to_s
end
# Write the bit value on silicon
# This method will update the data value of the bits and then call $top.write_register
# passing the owngin register as the first argument.
# This method is expected to handle writing the current state of the register to silicon.
def write!(value = nil, options = {})
value, options = nil, value if value.is_a?(Hash)
write(value, options) if value
@reg.request(:write_register, options)
self
end
# Similar to write! this method will perform the standard read method and then make
# a call to $top.read_register(self) with the expectation that this method will
# implement a read event in the pattern.
# ==== Example
# reg(:data).read! # Read register :data, expecting whatever value it currently holds
# reg(:data).read!(0x5555) # Read register :data, expecting 0x5555
def read!(value = nil, options = {})
value, options = nil, value if value.is_a?(Hash)
read(value, options)
# launch a read reg
@reg.request(:read_register, options)
self
end
# Normally whenever a register is processed by the $top.read_register method
# it will call Reg#clear_flags to acknowledge that the read has been performed,
# which clears the read and store flags for the given bits. Normally however you
# want overlays to stick around such that whenever a given bit is written/read its
# data is always picked from an overlay.
# Call this passing in false for a given register to cause the overlay data to also
# be cleared by Reg#clear_flags.
# ==== Example
# reg(:data).overlay("data_val")
# reg(:data).has_overlay? # => true
# reg(:data).clear_flags
# reg(:data).has_overlay? # => true
# reg(:data).sticky_overlay(false)
# reg(:data).clear_flags
# reg(:data).has_overlay? # => false
def sticky_overlay(set = true)
each { |bit| bit.sticky_overlay = set }
self
end
alias_method :sticky_overlays, :sticky_overlay
# Similar to sticky_overlay this method affects how the store flags are treated by
# Reg#clear_flags.
# The default is that store flags will get cleared by Reg#clear_flags, passing true
# into this method will override this and prevent them from clearing.
# ==== Example
# reg(:data).sticky_store(true)
# reg(:data).store
# reg(:data).clear_flags # Does not clear the request to store
def sticky_store(set = true)
each { |bit| bit.sticky_store = set }
self
end
# Marks all bits to be stored
def store
each(&:store)
self
end
# Marks all bits to be stored and then calls read!
def store!
store
read!
self
end
# Sets the store flag on all bits that already have the overlay flag set
# and then calls $top.read_register passing self as the first argument
def store_overlay_bits!(options = {})
store_overlay_bits(options)
@reg.request(:read_register, options) # Bypass the normal read method since we don't want to
# tag the other bits for read
self
end
# Sets the store flag on all bits that already have the overlay flag set
def store_overlay_bits(options = {})
options = { exclude: [], # Pass in an array of any overlays that are to be excluded from store
}.merge(options)
each do |bit|
bit.store if bit.has_overlay? && !options[:exclude].include?(bit.overlay_str)
end
self
end
# Will yield all unique overlay strings attached to the bits within the collection.
# It will also return the number of bits for the overlay (the length) and the current
# data value held in those bits.
# ==== Example
# reg(:control).unique_overlays do |str, length, data|
# do_something(str, length, data)
# end
def unique_overlays
current_overlay = false
length = 0
data = 0
shift_out_right do |bit|
# Init the current overlay when the first one is encountered
current_overlay = bit.overlay_str if bit.has_overlay? && !current_overlay
if bit.has_overlay?
if bit.overlay_str != current_overlay
yield current_overlay, length, data if current_overlay
length = 0
data = 0
end
data = data | (bit.data << length)
length += 1
else
yield current_overlay, length, data if current_overlay
length = 0
data = 0
current_overlay = false
end
end
yield current_overlay, length, data if current_overlay
end
# Append a value, for example a block identifier, to all overlays
# ==== Example
# reg(:data).overlay("data_val")
# reg(:data).append_overlays("_0")
# reg(:data).overlay_str # => "data_val_0"
def append_overlays(value)
each do |bit|
bit.overlay(bit.overlay_str + value) if bit.has_overlay?
end
self
end
# Delete the contained bits from the parent Register
def delete
@reg.delete_bits(self)
self
end
def add_name(name) # :nodoc:
if @name == :unknown
@name = name
elsif ![name].flatten.include?(name)
@name = [@name, name].flatten
end
self
end
def owner
first.owner
end
# All other methods send to bit 0
def method_missing(method, *args, &block) # :nodoc:
if first.respond_to?(method)
if size > 1
if [:meta, :meta_data, :metadata].include?(method.to_sym) ||
first.meta_data_method?(method)
first.send(method, *args, &block)
else
fail "Error, calling #{method} on a multi-bit collection is not implemented!"
end
else
first.send(method, *args, &block)
end
else
fail "BitCollection does not have a method named #{method}!"
end
end
# Recognize that BitCollection responds to some Bit methods via method_missing
def respond_to?(sym) # :nodoc:
first.respond_to?(sym) || super(sym)
end
# Returns true if the values of all bits in the collection are known. The value will be
# unknown in cases where the reset value is undefined or determined by a memory location
# and where the register has not been written or read to a specific value yet.
def has_known_value?
all?(&:has_known_value?)
end
# Returns the reset value of the collection, note that this does not reset the register and the
# current data is maintained.
#
# ==== Example
# reg(:control).write(0x55)
# reg(:control).data # => 0x55
# reg(:control).reset_data # => 0x11, assuming the reg was declared with a reset value of 0x11
# reg(:control).data # => 0x55
def reset_data(value = nil)
# This method was originally setup to set the reset value by passing an argument
if value
each_with_index { |bit, i| bit.reset_val = value[i] }
self
else
data = 0
each_with_index do |bit, i|
return bit.reset_data if bit.reset_data.is_a?(Symbol)
data |= bit.reset_data << i
end
data
end
end
alias_method :reset_val, :reset_data
alias_method :reset_value, :reset_data
alias_method :reset_data=, :reset_data
alias_method :reset_val=, :reset_data
alias_method :reset_value=, :reset_data
# Modify writable for bits in collection
def writable(value)
each_with_index { |bit, i| bit.writable = (value[i] == 0b1); bit.set_access_from_rw }
self
end
# Modify readable for bits in collection
def readable(value)
each_with_index { |bit, i| bit.readable = (value[i] == 0b1); bit.set_access_from_rw }
self
end
def feature
feature = []
feature << fetch(0).feature
each { |bit| feature << bit.feature if bit.has_feature_constraint? }
feature = feature.flatten.uniq unless feature.empty?
feature.delete(nil) if feature.include?(nil)
if !feature.empty?
if feature.size == 1
return feature[0]
else
return feature.uniq
end
else
if Origen.config.strict_errors
fail 'No feature found'
end
return nil
end
end
alias_method :features, :feature
# Return true if there is any feature associated with these bits
def has_feature_constraint?(name = nil)
if !name
any?(&:has_feature_constraint?)
else
any? { |bit| bit.enabled_by_feature?(name) }
end
end
alias_method :enabled_by_feature?, :has_feature_constraint?
def enabled?
all?(&:enabled?)
end
# Returns true if any bits in the collection are writable
def is_writable?
any?(&:writable?)
end
alias_method :writable?, :is_writable?
# Returns true if any bits in the collection are readable
def is_readable?
any?(&:readable?)
end
alias_method :readable?, :is_readable?
# Modify clr_only for bits in collection
def clr_only(value)
each_with_index { |bit, i| bit.clr_only = (value[i] == 0b1) }
self
end
# Modify set_only for bits in collection
def set_only(value)
each_with_index { |bit, i| bit.set_only = (value[i] == 0b1) }
self
end
# Return nvm_dep value held by collection
def nvm_dep
nvm_dep = 0
each_with_index { |bit, i| nvm_dep |= bit.nvm_dep << i }
nvm_dep
end
# Clear any w1c set bits back to 0
def clear_w1c
each(&:clear_w1c)
self
end
# Clear any start set bits back to 0
def clear_start
each(&:clear_start)
self
end
# Provides a string summary of the bit collection / register state that would be
# applied to given operation (write or read).
# This is mainly intended to be useful when generating pattern comments describing
# an upcoming register transaction.
#
# This highlights not only bit values bit the status of any flags or overlays that
# are currently set.
#
# The data is presented in hex nibble format with individual nibbles are expanded to
# binary format whenever all 4 bits do not have the same status - e.g. if only one
# of the four is marked for read.
#
# The following symbols are used to represent bit state:
#
# X - Bit is don't care (not marked for read)
# V - Bit has been tagged with an overlay
# S - Bit is marked for store
#
# @example
#
# myreg.status_str(:write) # => "0000"
# myreg.status_str(:read) # => "XXXX"
# myreg[7..4].read(5)
# myreg.status_str(:read) # => "XX5X"
# myreg[14].read(0)
# myreg.status_str(:read) # => "(x0xx)X5X"
def status_str(operation, options = {})
options = {
mark_overlays: true
}.merge(options)
str = ''
if operation == :read
shift_out_left do |bit|
if bit.is_to_be_stored?
str += STORE_CHAR
elsif bit.is_to_be_read?
if bit.has_overlay? && options[:mark_overlays]
str += OVERLAY_CHAR
else
str += bit.data.to_s
end
else
str += DONT_CARE_CHAR
end
end
elsif operation == :write
shift_out_left do |bit|
if bit.has_overlay? && options[:mark_overlays]
str += OVERLAY_CHAR
else
str += bit.data.to_s
end
end
else
fail "Unknown operation (#{operation}), must be :read or :write"
end
make_hex_like(str, size / 4)
end
private
# Converts a binary-like representation of a data value into a hex-like version.
# e.g. input => 010S0011SSSS0110 (where S, X or V represent store, don't care or overlay)
# output => (010s)3S6 (i.e. nibbles that are not all of the same type are expanded)
def make_hex_like(regval, size_in_nibbles)
outstr = ''
regex = '^'
size_in_nibbles.times { regex += '(....)' }
regex += '$'
Regexp.new(regex) =~ regval
nibbles = []
size_in_nibbles.times do |n| # now grouped by nibble
nibbles << Regexp.last_match[n + 1]
end
nibbles.each_with_index do |nibble, i|
# If contains any special chars...
if nibble =~ /[#{DONT_CARE_CHAR}#{STORE_CHAR}#{OVERLAY_CHAR}]/
# If all the same...
if nibble[0] == nibble[1] && nibble[1] == nibble[2] && nibble[2] == nibble[3]
outstr += nibble[0, 1] # .to_s
# Otherwise present this nibble in 'binary' format
else
outstr += "(#{nibble.downcase})"
end
# Otherwise if all 1s and 0s...
else
outstr += '%1X' % nibble.to_i(2)
end
end
outstr
end
# Cleans up indexed references to pins, e.g. makes these equal:
#
# bits(:data)[0,1,2,3]
# bits(:data)[3,2,1,0]
# bits(:data)[0..3]
# bits(:data)[3..0]
def expand_and_order(*indexes)
ixs = []
indexes.flatten.each do |index|
if index.is_a?(Range)
if index.first > index.last
ixs << (index.last..index.first).to_a
else
ixs << index.to_a
end
else
ixs << index
end
end
ixs.flatten.sort
end
end
end
end