module Origen module Specs class Spec autoload :Note, 'origen/specs/note' autoload :Exhibit, 'origen/specs/exhibit' include Checkers extend Checkers SpecAttribute = Struct.new(:name, :type, :required, :author, :description) Limit = Struct.new(:exp) do def value Origen::Specs::Spec.send(:evaluate_limit, exp) end end TYPES = Origen::Specs::SPEC_TYPES ATTRS = { ip_name: SpecAttribute.new(:ip_name, Symbol, true, :design, 'The parent IP object of the specification'), name: SpecAttribute.new(:name, Symbol, true, :design, 'Specification Name'), type: SpecAttribute.new(:type, Symbol, true, :design, "Specification Type, acceptable values: #{TYPES}"), sub_type: SpecAttribute.new(:sub_type, Symbol, true, :design, 'Specification sub-type (e.g. :max_operating_condition)'), mode: SpecAttribute.new(:mode, Symbol, true, :design, 'Specification mode, inherited from the owning parent object'), symbol: SpecAttribute.new(:symbol, String, false, :design, 'Specification symbol, can contain HTML'), description: SpecAttribute.new(:description, String, false, :design, 'Specification description'), audience: SpecAttribute.new(:audience, Symbol, false, :design, 'Specification audience, acceptable values are :internal and :external'), min: SpecAttribute.new(:min, Limit, false, :design, 'Specification minimum limit. The limit expression is displayed, not a resolved value'), min_ovr: SpecAttribute.new(:min_ovr, Limit, false, :design, 'Specification minimum limit at SoC level. The limit expression is displaye,d not a resolved value'), max: SpecAttribute.new(:max, Limit, false, :design, 'Specification maximum limit. The limit expression is displayed, not a resolved value'), max_ovr: SpecAttribute.new(:max_ovr, Limit, false, :design, 'Specification maximum limit at SoC level. The limit expression is displaye,d not a resolved value'), typ: SpecAttribute.new(:typ, Limit, false, :design, 'Specification typical limit. The limit expression is displayed, not a resolved value'), typ_ovr: SpecAttribute.new(:typ_ovr, Limit, false, :design, 'Specification typical limit at SoC level. The limit expression is displaye,d not a resolved value'), unit: SpecAttribute.new(:unit, String, false, :design, 'Specification unit of measure'), constraints: SpecAttribute.new(:constraints, String, false, :design, "Single logical expression or a CSV list of logical expressions required for the spec to be valid (e.g. 'GVDD == 1.2V'"), limit_type: SpecAttribute.new(:limit_type, Symbol, false, :design, 'Auto-generated attribute based on analysis of the spec limits. Acceptable values are :single_sided and :double_sided'), notes: SpecAttribute.new(:notes, Hash, false, :design, 'Specification notes'), disposition_required: SpecAttribute.new(:disposition_required, TrueClass, false, :pde, 'Boolean representation of whether a specification needs a disposition based on silicon results or customer input'), priority: SpecAttribute.new(:priority, TrueClass, false, :pde, 'Integer value (1-4) to indicate which priority the cz for this spec will be: 1. Highest priority, for critical or historically risky specs 2. Medium priority, relatively low risk. Not required until all priority 1 specs have been handled 3. Lowest priority, very low risk, low performance specs 4. No plans to characterize'), target: SpecAttribute.new(:target, String, false, :pde, 'Specification target limit. Not used for pass/fail results but for data analysis'), guardband: SpecAttribute.new(:guardband, Limit, false, :pde, 'Specification guardband limit'), testable: SpecAttribute.new(:testable, TrueClass, false, :pde, 'Boolean representation of whether a specification is testable'), tested_at_probe: SpecAttribute.new(:tested_at_probe, TrueClass, false, :pde, 'Boolean representation of whether a specification is tested at probe'), tested_at_ft_hot: SpecAttribute.new(:tested_at_ft_hot, TrueClass, false, :pde, 'Boolean representation of whether a specification is tested at final test hot temperature'), tested_at_ft_ext_hot: SpecAttribute.new(:tested_at_ft_ext_hot, TrueClass, false, :pde, 'Boolean representation of whether a specification is tested at final test extended hot temperature'), tested_at_ft_cold: SpecAttribute.new(:tested_at_ft_cold, TrueClass, false, :pde, 'Boolean representation of whether a specification is tested at final test cold temperature'), tested_at_ft_ext_cold: SpecAttribute.new(:tested_at_ft_ext_cold, TrueClass, false, :pde, 'Boolean representation of whether a specification is tested at final test extended cold temperature'), tested_at_ft_room: SpecAttribute.new(:tested_at_ft_room, TrueClass, false, :pde, 'Boolean representation of whether a specification is tested at final test room temperature'), guaranteed_by_prod_test: SpecAttribute.new(:guaranteed_by_prod_test, TrueClass, false, :pde, 'Boolean representation of whether a specification is guaranteed by production test'), guaranteed_by_proxy_test: SpecAttribute.new(:guaranteed_by_proxy_test, TrueClass, false, :pde, 'Boolean representation of whether a specification is guaranteed by production test via a proxy test such as BIST'), guaranteed_by_construction: SpecAttribute.new(:guaranteed_by_construction, TrueClass, false, :pde, 'Boolean representation of whether a specification is guaranteed by physical construction, design documentation required'), guaranteed_by_simulation: SpecAttribute.new(:guaranteed_by_simulation, TrueClass, false, :pde, 'Boolean representation of whether a specification is tested guaranteed by simulation, design documentation required'), cz_on_ate: SpecAttribute.new(:cz_on_ate, TrueClass, false, :pde, 'Boolean representation of whether a specification is characterized on ATE'), cz_ate_sample_size: SpecAttribute.new(:cz_ate_sample_size, Integer, false, :pde, 'Integer number representing the sample size of the split used for customer Cpk calculation as tested on ATE'), cz_ate_cpk: SpecAttribute.new(:cz_ate_cpk, Float, false, :pde, 'Float number representing the customer or representative Cpk of the specification as tested on ATE'), cz_on_bench: SpecAttribute.new(:cz_on_bench, TrueClass, false, :pde, 'Boolean representation of whether a specification is characterized on a bench setup'), cz_bench_sample_size: SpecAttribute.new(:cz_bench_sample_size, Integer, false, :pde, 'Integer number representing the sample size of the split used for customer Cpk calculation on a bench setup'), cz_bench_cpk: SpecAttribute.new(:cz_bench_cpk, Float, false, :pde, 'Float number representing the customer or representative Cpk of the specification as tested on a bench setup'), cz_on_system: SpecAttribute.new(:cz_on_system, TrueClass, false, :pde, 'Boolean representation of whether a specification is characterized in a system setup'), cz_system_sample_size: SpecAttribute.new(:cz_system_sample_size, Integer, false, :pde, 'Integer number representing the sample size of the split used for customer Cpk calculation in a system'), cz_system_cpk: SpecAttribute.new(:cz_system_cpk, Float, false, :pde, 'Float number representing the customer or representative Cpk of the specification as tested in a system') } ATTRS.each do |_id, spec_attr| class_eval("def #{spec_attr.name}(param=nil); param.nil? ? @#{spec_attr.name} : (@#{spec_attr.name} = param); end") end # There are at least three attributes needed to define a unique spec. # 1) name (e.g. :vdd) # 2) type (e.g. :dc) Possible values are [:dc, :ac, :temperature] # 3) mode (e.g. :global). mode defaults to the current mode found for the parent object # A mode is defined as a device state that requires some sequence of actions to be enabled. # A type is a classification moniker that exists without any stimulus required. # Some specs require a fourth attribute sub_type to be uniquely defined. # For example, a global device level VDD specification would require four attributes to be unique. # Here is an example of two spec definitions for a VDD power supply # name = :vdd, type: :dc, mode: :global, sub_type: typical_operating_conditions, typ = "1.0V +/- 30mV" # name = :vdd, type: :dc, mode: :global, sub_type: maximum_operating_conditions, min = -0.3V, max = 1.8V # Whereas a typical DDR timing specification might only need three attributes to be unique # name: :tddkhas, type: :ac, mode: ddr4dr2400, sub_type: nil def initialize(name, type, mode, owner_name, &block) @name = name_audit(name) fail 'Specification names must be of types Symbol or String and cannot start with a number' if @name.nil? @type = type @sub_type = nil # not necessary to be able to find a unique spec, but required for some specs @mode = mode @ip_name = owner_name @symbol = nil # Meant to be populated with HTML representing the way the spec name should look in a document @description = nil @min, @typ, @max, @target = nil, nil, nil, nil @min_ovr, @typ_ovr, @typ_ovr = nil, nil, nil @audience = nil @notes = {} @exhibits = {} @testable = nil @guardband = nil (block.arity < 1 ? (instance_eval(&block)) : block.call(self)) if block_given? fail "Spec type must be one of #{TYPES.join(', ')}" unless TYPES.include? type @min = Limit.new(@min) @max = Limit.new(@max) @typ = Limit.new(@typ) @min_ovr = Limit.new(@min_ovr) @max_ovr = Limit.new(@max_ovr) @typ_ovr = Limit.new(@typ_ovr) @guardband = Limit.new(@guardband) fail "Spec #{name} failed the limits audit!" unless limits_ok? end def inspect $dut.send(:specs_to_table_string, [self]) rescue super end def method_missing(method, *args, &block) ivar = "@#{method.to_s.gsub('=', '')}" ivar_sym = ":#{ivar}" if method.to_s =~ /=$/ define_singleton_method(method) do |val| instance_variable_set(ivar, val) end elsif instance_variables.include? ivar_sym instance_variable_get(ivar) else define_singleton_method(method) do instance_variable_get(ivar) end end send(method, *args, &block) end # Do a 'diff' from the current spec (self) and the compare spec # Returns a hash with attribute as key and an array of the # attribute values that differed def diff(compare_spec) diff_results = Hash.new do |h, k| h[k] = [] end # Loop through self's isntance variables first instance_variables.each do |ivar| ivar_sym = ivar.to_s.gsub('@', '').to_sym next if ivar_sym == :notes # temporarily disable until notes diff method written ivar_str = ivar.to_s.gsub('@', '') if compare_spec.respond_to? ivar_sym # Check if the instance variable is a Limit and if so then find # all instance_variables and diff them as well if instance_variable_get(ivar).class == Origen::Specs::Spec::Limit limit_diff_results = diff_limits(instance_variable_get(ivar), compare_spec.instance_variable_get(ivar)) # Extract the limit diff pairs and merge with updated keys with the diff_results hash limit_diff_results.each do |k, v| limit_diff_key = "#{ivar_str}_#{k}".to_sym diff_results[limit_diff_key] = v end else unless instance_variable_get(ivar) == compare_spec.instance_variable_get(ivar) diff_results[ivar_sym] = [instance_variable_get(ivar), compare_spec.instance_variable_get(ivar)] Origen.log.debug "Found spec difference for instance variable #{ivar} for #{self} and #{compare_spec}" end end else # The compare spec doesn't have the current instance variable # so log a difference if instance_variable_get(ivar).class == Origen::Specs::Spec::Limit limit_diff_results = diff_limits(instance_variable_get(ivar), compare_spec.instance_variable_get(ivar)) # Extract the limit diff pairs and merge with updated keys with the diff_results hash limit_diff_results.each do |k, v| limit_diff_key = "#{ivar_str}_#{k}".to_sym diff_results[limit_diff_key] = v end else Origen.log.debug "Instance variable #{ivar} exists for #{self} and does not for #{compare_spec}" diff_results[ivar_sym] = [instance_variable_get(ivar), ''] end end end # Loop through unique instance variables for compare_spec diff_results end # Monkey patch of hash/array include? method needed because # Origen::Specs#specs can return a single Spec instance or an Array of Specs def include?(s) s == @name ? true : false end # Add a specification note def add_note(id, options = {}) options = { type: :spec }.update(options) # Create the Note instance and add to the notes attribute @notes[id] = Origen::Specs::Note.new(id, options[:type], options) end # Returns a Note object from the notes hash def notes(id = nil) return nil if @notes.nil? @notes.filter(id) end # Returns the number of notes as an Integer def note_count @notes.size end private # This assumes the limit objects are Structs def diff_limits(limit_one, limit_two = nil) diff_results = Hash.new do |h, k| h[k] = [] end # Only need to loop through limit one ivars because the Limit class cannot # be changed in 3rd party files like the Spec class can be limit_one.members.each do |m| if limit_two.respond_to? m unless limit_one.send(m) == limit_two.send(m) diff_results[m] = [limit_one.send(m), limit_two.send(m)] Origen.log.debug "Found limit difference for member #{m} for #{limit_one} and #{limit_two}" end else # Limit two doesn't have the current instance variable or was not provided # as an argument so log a difference Origen.log.debug "Member #{m} exists for #{limit_one} and does not for #{limit_two}" diff_results[m] = [limit_one.send(m), ''] end end diff_results end end end end