From 1d7d8fe1f3f5c073e88cdbbdb686543bdfd2d20f Mon Sep 17 00:00:00 2001 From: Derek Hower Date: Thu, 3 Oct 2024 06:32:22 -0700 Subject: [PATCH 1/9] Fix misa length --- arch/csr/misa.yaml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/arch/csr/misa.yaml b/arch/csr/misa.yaml index 1ae8d493e..2dff7560e 100644 --- a/arch/csr/misa.yaml +++ b/arch/csr/misa.yaml @@ -4,12 +4,13 @@ misa: long_name: Machine ISA Control address: 0x301 priv_mode: M - length: 64 + length: MXLEN description: Reports the XLEN and "major" extensions supported by the ISA. definedBy: I fields: MXL: - location: 63-62 + location_rv32: 31-30 + location_rv64: 63-62 description: XLEN in M-mode. type: RO reset_value: 2 From 027d0a5d202f87a760fdd5124fa1afbddbba6e32 Mon Sep 17 00:00:00 2001 From: Derek Hower Date: Thu, 3 Oct 2024 06:34:04 -0700 Subject: [PATCH 2/9] Add git common directory as a bind to the container if using workspaces --- bin/setup | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/bin/setup b/bin/setup index 4b63fe45a..49d7ce185 100755 --- a/bin/setup +++ b/bin/setup @@ -19,6 +19,12 @@ else SINGULARITY_CACHE= fi +if [ -f $ROOT/.git ]; then + # if this is a worktree, need to add the parent git repo in, too + GIT_PATH=`git rev-parse --git-common-dir | tr -d '\n' | xargs dirname` + HOME_OPT="${HOME_OPT} --bind ${GIT_PATH}:${GIT_PATH}" +fi + if [ ! -d $ROOT/.home ]; then mkdir $ROOT/.home fi From e4cb70d41e0a4bb77731d610024c01b3cd471228 Mon Sep 17 00:00:00 2001 From: Derek Hower Date: Thu, 3 Oct 2024 06:36:02 -0700 Subject: [PATCH 3/9] Make three types of ArchDef: unconfigured, partially configured, fully configured (formerly ImplArchDef) --- arch/ext/Svade.yaml | 2 +- arch/ext/Svadu.yaml | 2 +- backends/arch_gen/lib/arch_gen.rb | 1 + backends/arch_gen/tasks.rake | 9 +- backends/cfg_html_doc/templates/csr.adoc.erb | 6 +- backends/manual/tasks.rake | 3 +- lib/arch_def.rb | 807 +++++++++++-------- lib/arch_obj_models/csr.rb | 135 +--- lib/arch_obj_models/csr_field.rb | 115 +-- lib/arch_obj_models/extension.rb | 6 + lib/arch_obj_models/instruction.rb | 2 +- lib/arch_obj_models/obj.rb | 18 + lib/idl/ast.rb | 66 +- lib/idl/symbol_table.rb | 34 +- lib/idl/tests/helpers.rb | 3 - lib/validate.rb | 3 +- schemas/arch_schema.json | 44 +- schemas/csr_schema.json | 19 +- schemas/ext_schema.json | 18 +- schemas/schema_defs.json | 7 + 20 files changed, 717 insertions(+), 583 deletions(-) diff --git a/arch/ext/Svade.yaml b/arch/ext/Svade.yaml index 896a393cd..75f5b6a83 100644 --- a/arch/ext/Svade.yaml +++ b/arch/ext/Svade.yaml @@ -33,7 +33,7 @@ Svade: - name: Paul Donahue - name: Ved Shanbhogue company: Rivos, Inc. - conflicts: Svadu + conflicts: Svadu doc_license: name: Creative Commons Attribution 4.0 International License (CC-BY 4.0) url: https://creativecommons.org/licenses/by/4.0/ diff --git a/arch/ext/Svadu.yaml b/arch/ext/Svadu.yaml index 0eed00c1f..6dbb8cb07 100644 --- a/arch/ext/Svadu.yaml +++ b/arch/ext/Svadu.yaml @@ -116,7 +116,7 @@ Svadu: - name: Paul Donahue - name: Ved Shanbhogue company: Rivos, Inc. - conflicts: Svade + conflicts: Svade doc_license: name: Creative Commons Attribution 4.0 International License (CC-BY 4.0) url: https://creativecommons.org/licenses/by/4.0/ diff --git a/backends/arch_gen/lib/arch_gen.rb b/backends/arch_gen/lib/arch_gen.rb index cd574bfb4..e9b049c83 100644 --- a/backends/arch_gen/lib/arch_gen.rb +++ b/backends/arch_gen/lib/arch_gen.rb @@ -278,6 +278,7 @@ def gen_arch_def end.to_h arch_def = { + "type" => "fully configured", "params" => params, "instructions" => inst_hash, "implemented_instructions" => @implemented_instructions, diff --git a/backends/arch_gen/tasks.rake b/backends/arch_gen/tasks.rake index 84ae47e5f..65ca812e9 100644 --- a/backends/arch_gen/tasks.rake +++ b/backends/arch_gen/tasks.rake @@ -13,9 +13,13 @@ def arch_def_for(config_name) @arch_defs[config_name] = if config_name == "_" - ArchDef.new + ArchDef.new("_", $root / "gen" / "_" / "arch" / "arch_def.yaml") else - ImplArchDef.new(config_name) + ArchDef.new( + config_name, + $root / "gen" / config_name / "arch" / "arch_def.yaml", + overlay_path: $root / "cfgs" / config_name / "arch_overlay" + ) end end @@ -103,6 +107,7 @@ file "#{$root}/.stamps/arch-gen.stamp" => ( end.to_h arch_def = { + "type" => "unconfigured", "instructions" => inst_hash, "extensions" => ext_hash, "csrs" => csr_hash, diff --git a/backends/cfg_html_doc/templates/csr.adoc.erb b/backends/cfg_html_doc/templates/csr.adoc.erb index 1586c2982..0ceb96ce4 100644 --- a/backends/cfg_html_doc/templates/csr.adoc.erb +++ b/backends/cfg_html_doc/templates/csr.adoc.erb @@ -29,7 +29,7 @@ h| Privilege Mode | <%= csr.priv_mode %> .<%= csr.name %> format [wavedrom, ,svg,subs='attributes',width="100%"] .... -<%= JSON.dump csr.wavedrom_desc(arch_def, arch_def.param_values["XLEN"]) %> +<%= JSON.dump csr.wavedrom_desc(arch_def, arch_def.param_values["XLEN"], exclude_unimplmented: true) %> .... <%- else -%> <%# CSR has a dynamic length, or a field has a dynamic location, @@ -39,13 +39,13 @@ This CSR format changes dynamically. .<%= csr.name %> Format when <%= csr.length_cond32 %> [wavedrom, ,svg,subs='attributes',width="100%"] .... -<%= JSON.dump csr.wavedrom_desc(arch_def, 32) %> +<%= JSON.dump csr.wavedrom_desc(arch_def, 32, exclude_unimplmented: true) %> .... .<%= csr.name %> Format when <%= csr.length_cond64 %> [wavedrom, ,svg,subs='attributes',width="100%"] .... -<%= JSON.dump csr.wavedrom_desc(arch_def, 64) %> +<%= JSON.dump csr.wavedrom_desc(arch_def, 64, exclude_unimplmented: true) %> .... diff --git a/backends/manual/tasks.rake b/backends/manual/tasks.rake index 1e6c0fb0d..5da921eb4 100644 --- a/backends/manual/tasks.rake +++ b/backends/manual/tasks.rake @@ -347,9 +347,10 @@ end Dir.glob($root / "arch" / "manual" / "**" / "contents.yaml") do |content_fn| file "#{File.dirname(content_fn)}/riscv-isa-manual/README.md" => ($root / "ext" / "riscv-isa-manual" / "README.md").to_s do |t| content_obj = YAML.load_file(content_fn) + git_dir = `git rev-parse --git-dir`.strip cmd = [ "git", - "--git-dir=#{$root}/.git/modules/ext/riscv-isa-manual", + "--git-dir=#{git_dir}/modules/ext/riscv-isa-manual", "worktree add", File.dirname(t.name), content_obj["isa_manual_tree"], diff --git a/lib/arch_def.rb b/lib/arch_def.rb index 3fb12b7bf..7d2fbcae5 100644 --- a/lib/arch_def.rb +++ b/lib/arch_def.rb @@ -26,20 +26,71 @@ class ArchDef # @return [Idl::AstNode] Abstract syntax tree of global scope attr_reader :global_ast - def name = "_" + # @return [String] Name of this definition. Special names are: + # * '_' - The generic architecture, with no configuration settings. + # * '_32' - A generic RV32 architecture, with only one parameter set (XLEN == 32) + # * '_64' - A generic RV64 architecture, with only one parameter set (XLEN == 64) + attr_reader :name + + # @return [Hash] A hash mapping parameter name to value for any parameter that has been configured with a value. May be empty. + attr_reader :param_values + + # @return [Integer] 32 or 64, the XLEN in M-mode + # @return [nil] if the XLEN in M-mode is not configured + attr_reader :mxlen + + # hash for Hash lookup + def hash = @name.hash + + # @return [Idl::SymbolTable] Symbol table with global scope + # @return [nil] if the architecture is not configured (use sym_table_32 or sym_table_64) + def sym_table + raise NotImplementedError, "Un-configured ArchDefs have no symbol table" if @sym_table.nil? + + @sym_table + end + + def fully_configured? = @arch_def["type"] == "fully configured" + def partially_configured? = @arch_def["type"] == "partially configured" + def unconfigured? = @arch_def["type"] == "unconfigured" + def type = @arch_def["type"] # Initialize a new configured architecture defintiion # # @param config_name [#to_s] The name of a configuration, which must correspond # to a folder under $root/cfgs - def initialize(from_child: false) + def initialize(config_name, arch_def_path, overlay_path: nil) + @name = config_name.to_s @idl_compiler = Idl::Compiler.new(self) - unless from_child - arch_def_file = $root / "gen" / "_" / "arch" / "arch_def.yaml" + validator = Validator.instance + begin + validator.validate_str(arch_def_path.read, type: :arch) + rescue Validator::SchemaValidationError => e + warn "While parsing unified architecture definition at #{arch_def_path}" + raise e + end + + @arch_def = YAML.load_file(arch_def_path, permitted_classes: [Date]) + @param_values = @arch_def.key?("params") ? @arch_def["params"] : {} + @mxlen = @arch_def.dig("params", "XLEN") # might be nil - @arch_def = YAML.load_file(arch_def_file, permitted_classes: [Date]) + unless @mxlen.nil? + # need at least XLEN specified to have a full architecture definition + # to populate the symbol table. + # + # if this is the fully generic config ("_"), then you need to use + # either sym_table_32 or sym_table_64 + @sym_table = Idl::SymbolTable.new(self) + custom_globals_path = overlay_path.nil? ? Pathname.new("/does/not/exist") : overlay_path / "isa" / "globals.isa" + idl_path = File.exist?(custom_globals_path) ? custom_globals_path : $root / "arch" / "isa" / "globals.isa" + @global_ast = @idl_compiler.compile_file( + idl_path, + symtab: @sym_table + ) + @sym_table.deep_freeze + else # parse globals @global_ast = @idl_compiler.compile_file( $root / "arch" / "isa" / "globals.isa", @@ -53,7 +104,6 @@ def initialize(from_child: false) symtab: sym_table_64 ) sym_table_64.deep_freeze - end end @@ -62,6 +112,8 @@ def initialize(from_child: false) # # @return [Idl::SymbolTable] Symbol table with config-independent global symbols populated for RV32 def sym_table_32 + raise NotImplementedError, "Use sym_table for configured def" unless @sym_table.nil? + return @sym_table_32 unless @sym_table_32.nil? @sym_table_32 = Idl::SymbolTable.new(self, 32) @@ -72,16 +124,155 @@ def sym_table_32 # # @return [Idl::SymbolTable] Symbol table with config-independent global symbols populated for RV64 def sym_table_64 + raise NotImplementedError, "Use sym_table for configured def" unless @sym_table.nil? + return @sym_table_64 unless @sym_table_64.nil? @sym_table_64 = Idl::SymbolTable.new(self, 64) end - def possible_xlens = [32, 64] + # Returns whether or not it may be possible to switch XLEN given this definition. + # + # There are three cases when this will return true: + # 1. A mode (e.g., U) is known to be implemented, and the CSR bit that controls XLEN in that mode is known to be writeable. + # 2. A mode is known to be implemented, but the writability of the CSR bit that controls XLEN in that mode is not known. + # 3. It is not known if the mode is implemented. + # + # + # @return [Boolean] true if this configuration might execute in multiple xlen environments + # (e.g., that in some mode the effective xlen can be either 32 or 64, depending on CSR values) + def multi_xlen? + return true if @mxlen.nil? + + ["S", "U", "VS", "VU"].any? { |mode| multi_xlen_in_mode?(mode) } + end + + # Returns whether or not it may be possible to switch XLEN in +mode+ given this definition. + # + # There are three cases when this will return true: + # 1. +mode+ (e.g., U) is known to be implemented, and the CSR bit that controls XLEN in +mode+ is known to be writeable. + # 2. +mode+ is known to be implemented, but the writability of the CSR bit that controls XLEN in +mode+ is not known. + # 3. It is not known if +mode+ is implemented. + # + # Will return false if +mode+ is not possible (e.g., because U is a prohibited extension) + # + # @param mode [String] mode to check. One of "M", "S", "U", "VS", "VU" + # @return [Boolean] true if this configuration might execute in multiple xlen environments in +mode+ + # (e.g., that in some mode the effective xlen can be either 32 or 64, depending on CSR values) + def multi_xlen_in_mode?(mode) + case mode + when "M" + mxlen.nil? + when "S" + return true if unconfigured? + + if fully_configured? + ext?(:S) && (@param_values["SXLEN"] == 3264) + elsif partially_configured? + return false if prohibited_ext?(:S) + + return true unless ext?(:S) # if S is not known to be implemented, we can't say anything about it + + return true unless @param_values.key?("SXLEN") + + @param_values["SXLEN"] == 3264 + else + raise "Unexpected configuration state" + end + when "U" + return false if prohibited_ext?(:U) + + return true if unconfigured? + + if fully_configured? + ext?(:U) && (@param_values["UXLEN"] == 3264) + elsif partially_configured? + return true unless ext?(:U) # if U is not known to be implemented, we can't say anything about it + + return true unless @param_values.key?("UXLEN") + + @param_values["UXLEN"] == 3264 + else + raise "Unexpected configuration state" + end + when "VS" + return false if prohibited_ext?(:H) + + return true if unconfigured? + + if fully_configured? + ext?(:H) && (@param_values["VSXLEN"] == 3264) + elsif partially_configured? + return true unless ext?(:H) # if H is not known to be implemented, we can't say anything about it + + return true unless @param_values.key?("VSXLEN") + + @param_values["VSXLEN"] == 3264 + else + raise "Unexpected configuration state" + end + when "VU" + return false if prohibited_ext?(:H) + + return true if unconfigured? + + if fully_configured? + ext?(:H) && (@param_values["VUXLEN"] == 3264) + elsif partially_configured? + return true unless ext?(:H) # if H is not known to be implemented, we can't say anything about it + + return true unless @param_values.key?("VUXLEN") + + @param_values["VUXLEN"] == 3264 + else + raise "Unexpected configuration state" + end + else + raise ArgumentError, "Bad mode" + end + end + + # @return [Array] List of possible XLENs in any mode for this config + def possible_xlens = multi_xlen? ? [32, 64] : [mxlen] + + # @return [Array] List of all available parameters with known values for the config + def params_with_value + return @params_with_value unless @params_with_value.nil? + + @params_with_value = [] + extensions.each do |ext_version| + ext = extension(ext_version.name) + ext.params.each do |ext_param| + if param_values.key?(ext_param.name) + @params_with_value << ExtensionParameterWithValue.new( + ext_param, + param_values[ext_param.name] + ) + end + end + end + @params_with_value + end + + # @return [Array] List of all available parameters without known values for the config + def params_without_value + return @params_without_value unless @params_without_value.nil? + + @params_without_value = [] + extensions.each do |ext_version| + ext = extension(ext_version.name) + ext.params.each do |ext_param| + unless param_values.key?(ext_param.name) + @params_without_value << ext_param + end + end + end + @params_without_value + end # Returns a string representation of the object, suitable for debugging. # @return [String] A string representation of the object. - def inspect = "ArchDef" + def inspect = "ArchDef##{name}" # @return [Array] List of all extensions, even those that are't implemented def extensions @@ -94,6 +285,41 @@ def extensions @extensions end + # may be overridden by subclass + # @return [Array] List of all extensions known to be implemented in this architecture + def implemented_extensions + return @implemented_extensions unless @implemented_extensions.nil? + + @implemented_extensions = [] + if @arch_def.key?("implemented_extensions") + @arch_def["implemented_extensions"].each do |e| + @implemented_extensions << ExtensionVersion.new(e["name"], e["version"]) + end + end + @implemented_extensions + end + + # @return [Array] List of extensions that are explicitly prohibited by an arch def + def prohibited_extensions + return @prohibited_extensions unless @prohibited_extensions.nil? + + @prohibited_extensions = [] + if @arch_def.key?("prohibited_extensions") + @arch_def["prohibited_extensions"].each do |e| + if e.is_a?(String) + @prohibited_extensions << ExtensionRequirement.new(e, nil) + else + @prohibited_extensions << ExtensionRequirement.new(e["name"], e["requirements"]) + end + end + end + @prohibited_extensions + end + + def prohibited_ext?(ext_name) + prohibited_extensions.any? { |ext_req| ext_req.name == ext_name.to_s } + end + # @return [Hash] Hash of all extensions, even those that aren't implemented, indexed by extension name def extension_hash return @extension_hash unless @extension_hash.nil? @@ -112,6 +338,47 @@ def extension(name) extension_hash[name.to_s] end + # @overload ext?(ext_name) + # @param ext_name [#to_s] Extension name (case sensitive) + # @return [Boolean] True if the extension `name` is implemented + # @overload ext?(ext_name, ext_version_requirements) + # @param ext_name [#to_s] Extension name (case sensitive) + # @param ext_version_requirements [Number,String,Array] Extension version requirements, taking the same inputs as Gem::Requirement + # @see https://docs.ruby-lang.org/en/3.0/Gem/Requirement.html#method-c-new Gem::Requirement#new + # @return [Boolean] True if the extension `name` meeting `ext_version_requirements` is implemented + # @example Checking extension presence with a version requirement + # arch_def.ext?(:S, ">= 1.12") + # @example Checking extension presence with multiple version requirements + # arch_def.ext?(:S, ">= 1.12", "< 1.15") + # @example Checking extension precsence with a precise version requirement + # arch_def.ext?(:S, 1.12) + def ext?(ext_name, *ext_version_requirements) + @ext_cache ||= {} + cached_result = @ext_cache[[ext_name, ext_version_requirements]] + return cached_result unless cached_result.nil? + + result = + implemented_extensions.any? do |e| + if ext_version_requirements.empty? + e.name == ext_name.to_s + else + requirement = Gem::Requirement.new(ext_version_requirements) + (e.name == ext_name.to_s) && requirement.satisfied_by?(e.version) + end + end + @ext_cache[[ext_name, ext_version_requirements]] = result + end + + # @return [Array] Array of all extensions that are prohibited because they are excluded by an implemented extension + def conflicting_extensions + extensions.map(&:conflicts).flatten + end + + # @return [Boolean] whether or not ext_name is prohibited because it is excluded by an implemented extension + def conflicting_ext?(ext_name) + prohibited_extensions.include? { |ext_req| ext_req.name == ext_name } + end + # @return [Array] List of all parameters defined in the architecture def params return @params unless @params.nil? @@ -348,7 +615,7 @@ def csc_crds_hash def csc_crd(name) = csc_crds_hash[name] - # @return [Array] All exception codes defined by extensions + # @return [Array] All exception codes defined by RISC-V def exception_codes return @exception_codes unless @exception_codes.nil? @@ -367,6 +634,29 @@ def exception_codes end end + # @return [Array] All exception codes known to be implemented + def implemented_exception_codes + return @implemented_exception_codes unless @implemented_exception_codes.nil? + + @implemented_exception_codes = + implemented_extensions.reduce([]) do |list, ext_version| + ecodes = extension(ext_version.name)["exception_codes"] + next list if ecodes.nil? + + ecodes.each do |ecode| + # double check that all the codes are unique + raise "Duplicate exception code" if list.any? { |e| e.num == ecode["num"] || e.name == ecode["name"] || e.var == ecode["var"] } + + unless ecode.dig("when", "version").nil? + # check version + next unless ext?(ext_version.name.to_sym, ecode["when"]["version"]) + end + list << ExceptionCode.new(ecode["name"], ecode["var"], ecode["num"], self) + end + list + end + end + # @return [Array] All interrupt codes defined by extensions def interrupt_codes return @interrupt_codes unless @interrupt_codes.nil? @@ -388,165 +678,135 @@ def interrupt_codes end end - # given an adoc string, find names of CSR/Instruction/Extension enclosed in `monospace` - # and replace them with links to the relevant object page - # - # @param adoc [String] Asciidoc source - # @return [String] Asciidoc source, with link placeholders - def find_replace_links(adoc) - adoc.gsub(/`([\w.]+)`/) do |match| - name = Regexp.last_match(1) - csr_name, field_name = name.split(".") - csr = csr(csr_name) - if !field_name.nil? && !csr.nil? && csr.field?(field_name) - "%%LINK%csr_field;#{csr_name}.#{field_name};#{csr_name}.#{field_name}%%" - elsif !csr.nil? - "%%LINK%csr;#{csr_name};#{csr_name}%%" - elsif inst(name) - "%%LINK%inst;#{name};#{name}%%" - elsif extension(name) - "%%LINK%ext;#{name};#{name}%%" - else - match - end - end - end - - # Returns an environment hash suitable for use with ERb templates. - # - # This method returns a hash containing the architecture definition and other - # relevant data that can be used to generate ERb templates. - # - # @return [Hash] An environment hash suitable for use with ERb templates. - def erb_env - return @env unless @env.nil? + # @return [Array] All interrupt codes known to be implemented + def implemented_interrupt_codes + return @implemented_interrupt_codes unless @implemented_interrupt_codes.nil? - @env = Class.new - @env.instance_variable_set(:@cfg, @cfg) - @env.instance_variable_set(:@params, @params) - @env.instance_variable_set(:@arch_gen, self) - - @env.instance_exec do - # method to check if a given extension (with an optional version number) is present - # - # @param ext_name [String,#to_s] Name of the extension - # @param ext_requirement [String, #to_s] Version string, as a Gem Requirement (https://guides.rubygems.org/patterns/#pessimistic-version-constraint) - # @return [Boolean] whether or not extension +ext_name+ meeting +ext_requirement+ is implemented in the config - def ext?(ext_name, ext_requirement = ">= 0") - true # ? - end + @implemented_interupt_codes = + implemented_extensions.reduce([]) do |list, ext_version| + icodes = extension(ext_version.name)["interrupt_codes"] + next list if icodes.nil? - # @return [Array] List of possible XLENs for any implemented mode - def possible_xlens - [32, 64] - end + icodes.each do |icode| + # double check that all the codes are unique + raise "Duplicate interrupt code" if list.any? { |i| i.num == icode["num"] || i.name == icode["name"] || i.var == icode["var"] } - # insert a hyperlink to an object - # At this point, we insert a placeholder since it will be up - # to the backend to create a specific link - # - # @params type [Symbol] Type (:section, :csr, :inst, :ext) - # @params name [#to_s] Name of the object - def link_to(type, name) - "%%LINK%#{type};#{name}%%" + unless ecode.dig("when", "version").nil? + # check version + next unless ext?(ext_version.name.to_sym, ecode["when"]["version"]) + end + list << InterruptCode.new(icode["name"], icode["var"], icode["num"], self) + end + list end + end - # info on interrupt and exception codes + # @return [Hash] The raw architecture defintion data structure + def data + @arch_def + end - # @returns [Hash] architecturally-defined exception codes and their names - def exception_codes - @arch_gen.exception_codes - end + # @return [Array] List of all implemented CSRs + def implemented_csrs + return @implemented_csrs unless @implemented_csrs.nil? - # returns [Hash] architecturally-defined interrupt codes and their names - def interrupt_codes - @arch_gen.interrupt_codes + @implemented_csrs = + if @arch_def.key?("implemented_csrs") + csrs.select { |c| @arch_def["implemented_csrs"].include?(c.name) } + else + [] end - end - - @env end - private :erb_env - # passes _erb_template_ through ERB within the content of this config - # - # @param erb_template [String] ERB source - # @return [String] The rendered text - def render_erb(erb_template, what='') - t = Tempfile.new("template") - t.write erb_template - t.flush - begin - Tilt["erb"].new(t.path, trim: "-").render(erb_env) - rescue - warn "While rendering ERB template: #{what}" - raise - ensure - t.close - t.unlink + # @return [Hash] Implemented csrs, indexed by CSR name + def implemented_csr_hash + return @implemented_csr_hash unless @implemented_csr_hash.nil? + + @implemented_csr_hash = {} + implemented_csrs.each do |csr| + @implemented_csr_hash[csr.name] = csr end + @implemented_csr_hash end -end -# a synchroncous exception code -class ExceptionCode - # @return [String] Long-form display name (can include special characters) - attr_reader :name - - # @return [String] Field name for an IDL enum - attr_reader :var - - # @return [Integer] Code, written into *mcause - attr_reader :num + # @param csr_name [#to_s] CSR name + # @return [Csr,nil] a specific csr, or nil if it doesn't exist or isn't implemented + def implemented_csr(csr_name) + implemented_csr_hash[csr_name] + end - # @return [Extension] Extension that defines this code - attr_reader :ext + # @return [Array] List of all implemented instructions + def implemented_instructions + return @implemented_instructions unless @implemented_instructions.nil? - def initialize(name, var, number, ext) - @name = name - @var = var - @num = number - @ext = ext + @implemented_instructions = + if @arch_def.key?("implemented_instructions") + @arch_def["implemented_instructions"].map do |inst_name| + instruction_hash[inst_name] + end + else + [] + end end -end -# all the same informatin as ExceptinCode, but for interrupts -InterruptCode = Class.new(ExceptionCode) -# Object model for a configured architecture definition -class ImplArchDef < ArchDef - # @return [String] Name of the architecture configuration - attr_reader :name + # @return [Array] List of all reachable IDL functions for the config + def implemented_functions + return @implemented_functions unless @implemented_functions.nil? - # @return [SymbolTable] The symbol table containing global definitions - attr_reader :sym_table + @implemented_functions = [] - # @return [Hash] The configuration parameter name => value - attr_reader :param_values + puts " Finding all reachable functions from instruction operations" - # @return [Integer] 32 or 64, the XLEN in m-mode - attr_reader :mxlen + implemented_instructions.each do |inst| + @implemented_functions << + if inst.base.nil? + if multi_xlen? + (inst.reachable_functions(sym_table, 32) + + inst.reachable_functions(sym_table, 64)) + else + inst.reachable_functions(sym_table, mxlen) + end + else + inst.reachable_functions(sym_table, inst.base) + end + end + @implemented_functions.flatten!.uniq!(&:name) - # hash for Hash lookup - def hash = @name.hash + puts " Finding all reachable functions from CSR operations" - # @return [Array] List of all available parameters for the config - def params_with_value - return @params_with_value unless @params_with_value.nil? + implemented_csrs.each do |csr| + csr_funcs = csr.reachable_functions(self) + csr_funcs.each do |f| + @implemented_functions << f unless @implemented_functions.any? { |i| i.name == f.name } + end + end - @params_with_value = [] - implemented_extensions.each do |ext_version| - ext = extension(ext_version.name) - ext.params.each do |ext_param| - if param_values.key?(ext_param.name) - @params_with_value << ExtensionParameterWithValue.new( - ext_param, - param_values[ext_param.name] - ) - end + @implemented_functions + end + + # given an adoc string, find names of CSR/Instruction/Extension enclosed in `monospace` + # and replace them with links to the relevant object page + # + # @param adoc [String] Asciidoc source + # @return [String] Asciidoc source, with link placeholders + def find_replace_links(adoc) + adoc.gsub(/`([\w.]+)`/) do |match| + name = Regexp.last_match(1) + csr_name, field_name = name.split(".") + csr = csr(csr_name) + if !field_name.nil? && !csr.nil? && csr.field?(field_name) + "%%LINK%csr_field;#{csr_name}.#{field_name};#{csr_name}.#{field_name}%%" + elsif !csr.nil? + "%%LINK%csr;#{csr_name};#{csr_name}%%" + elsif inst(name) + "%%LINK%inst;#{name};#{name}%%" + elsif extension(name) + "%%LINK%ext;#{name};#{name}%%" + else + match end end - @params_with_value end # Returns an environment hash suitable for use with ERb templates. @@ -568,6 +828,10 @@ def erb_env @env.const_set(param.name, param.value) unless @env.const_defined?(param.name) end + params_without_value.each do |param| + @env.const_set(param.name, :unknown) unless @env.const_defined?(param.name) + end + @env.instance_exec do # method to check if a given extension (with an optional version number) is present # @@ -604,236 +868,63 @@ def exception_codes def interrupt_codes @arch_gen.interrupt_codes end - end - - @env - end - private :erb_env - - # Initialize a new configured architecture defintiion - # - # @param config_name [#to_s] The name of a configuration, which must correspond - # to a folder under $root/cfgs - def initialize(config_name) - super(from_child: true) - - @name = config_name.to_s - arch_def_file = $root / "gen" / @name / "arch" / "arch_def.yaml" - - validator = Validator.instance - begin - validator.validate_str(arch_def_file.read, type: :arch) - rescue Validator::SchemaValidationError => e - warn "While parsing unified architecture definition at #{arch_def_file}" - raise e - end - - @arch_def = YAML.load_file(arch_def_file) - - @param_values = @arch_def["params"] - @mxlen = @arch_def["params"]["XLEN"] - - @sym_table = Idl::SymbolTable.new(self) - - # load the globals into the symbol table - custom_globals_path = $root / "cfgs" / @name / "arch_overlay" / "isa" / "globals.isa" - idl_path = File.exist?(custom_globals_path) ? custom_globals_path : $root / "arch" / "isa" / "globals.isa" - @global_ast = @idl_compiler.compile_file( - idl_path, - symtab: @sym_table - ) - - @sym_table.deep_freeze - end - - def inspect = "ArchDef##{name}" - - # @return [Boolean] true if this configuration can execute in multiple xlen environments - # (i.e., that in some mode the effective xlen can be either 32 or 64, depending on CSR values) - def multi_xlen? - ["SXLEN", "UXLEN", "VSXLEN", "VUXLEN"].any? { |key| @param_values[key] == 3264 } - end - - # @return [Array] List of possible XLENs in any mode for this config - def possible_xlens - multi_xlen? ? [32, 64] : [mxlen] - end - - # @param mode [String] One of ['M', 'S', 'U', 'VS', 'VU'] - # @return [Boolean] whether or not XLEN can change in the mode - def multi_xlen_in_mode?(mode) - case mode - when "M" - false - when "S" - @param_values["SXLEN"] == 3264 - when "U" - @param_values["UXLEN"] == 3264 - when "VS" - @param_values["VSXLEN"] == 3264 - when "VU" - @param_values["VUXLEN"] == 3264 - else - raise ArgumentError, "Bad mode" - end - end - - # @return [Array] List of all extensions, with specific versions, that are implemented - def implemented_extensions - return @implemented_extensions unless @implemented_extensions.nil? - - @implemented_extensions = [] - @arch_def["implemented_extensions"].each do |e| - @implemented_extensions << ExtensionVersion.new(e["name"], e["version"]) - end - - @implemented_extensions - end - - # @overload ext?(ext_name) - # @param ext_name [#to_s] Extension name (case sensitive) - # @return [Boolean] True if the extension `name` is implemented - # @overload ext?(ext_name, ext_version_requirements) - # @param ext_name [#to_s] Extension name (case sensitive) - # @param ext_version_requirements [Number,String,Array] Extension version requirements, taking the same inputs as Gem::Requirement - # @see https://docs.ruby-lang.org/en/3.0/Gem/Requirement.html#method-c-new Gem::Requirement#new - # @return [Boolean] True if the extension `name` meeting `ext_version_requirements` is implemented - # @example Checking extension presence with a version requirement - # arch_def.ext?(:S, ">= 1.12") - # @example Checking extension presence with multiple version requirements - # arch_def.ext?(:S, ">= 1.12", "< 1.15") - # @example Checking extension precsence with a precise version requirement - # arch_def.ext?(:S, 1.12) - def ext?(ext_name, *ext_version_requirements) - @ext_cache ||= {} - cached_result = @ext_cache[[ext_name, ext_version_requirements]] - return cached_result unless cached_result.nil? - - result = - implemented_extensions.any? do |e| - if ext_version_requirements.empty? - e.name == ext_name.to_s - else - requirement = Gem::Requirement.new(ext_version_requirements) - (e.name == ext_name.to_s) && requirement.satisfied_by?(e.version) - end - end - @ext_cache[[ext_name, ext_version_requirements]] = result - end - - # @return [Array] All exception codes from this implementation - def exception_codes - return @exception_codes unless @exception_codes.nil? - @exception_codes = - implemented_extensions.reduce([]) do |list, ext_version| - ecodes = extension(ext_version.name)["exception_codes"] - next list if ecodes.nil? - - ecodes.each do |ecode| - # double check that all the codes are unique - raise "Duplicate exception code" if list.any? { |e| e.num == ecode["num"] || e.name == ecode["name"] || e.var == ecode["var"] } - - list << ExceptionCode.new(ecode["name"], ecode["var"], ecode["num"], self) - end - list + # @returns [Hash] architecturally-defined exception codes and their names + def implemented_exception_codes + @arch_gen.implemented_exception_codes end - end - - # @return [Array] All interrupt codes from this implementation - def interrupt_codes - return @interrupt_codes unless @interrupt_codes.nil? - - @interupt_codes = - implemented_extensions.reduce([]) do |list, ext_version| - icodes = extension(ext_version.name)["interrupt_codes"] - next list if icodes.nil? - - icodes.each do |icode| - # double check that all the codes are unique - raise "Duplicate interrupt code" if list.any? { |i| i.num == icode["num"] || i.name == icode["name"] || i.var == icode["var"] } - list << InterruptCode.new(icode["name"], icode["var"], icode["num"], self) - end - list + # returns [Hash] architecturally-defined interrupt codes and their names + def implemented_interrupt_codes + @arch_gen.implemented_interrupt_codes end - end - - # @return [Hash] The raw architecture defintion data structure - def data - @arch_def - end - - # @return [Array] List of all implemented CSRs - def implemented_csrs - return @implemented_csrs unless @implemented_csrs.nil? - - @implemented_csrs = csrs.select { |c| @arch_def["implemented_csrs"].include?(c.name) } - end - - # @return [Hash] Implemented csrs, indexed by CSR name - def implemented_csr_hash - return @implemented_csr_hash unless @implemented_csr_hash.nil? - - @implemented_csr_hash = {} - implemented_csrs.each do |csr| - @implemented_csr_hash[csr.name] = csr end - @implemented_csr_hash - end - - - # @param csr_name [#to_s] CSR name - # @return [Csr,nil] a specific csr, or nil if it doesn't exist or isn't implemented - def implemented_csr(csr_name) - implemented_csr_hash[csr_name] + @env end + private :erb_env - # @return [Array] List of all implemented instructions - def implemented_instructions - return @implemented_instructions unless @implemented_instructions.nil? - - @implemented_instructions = @arch_def["implemented_instructions"].map do |inst_name| - instruction_hash[inst_name] + # passes _erb_template_ through ERB within the content of this config + # + # @param erb_template [String] ERB source + # @return [String] The rendered text + def render_erb(erb_template, what='') + t = Tempfile.new("template") + t.write erb_template + t.flush + begin + Tilt["erb"].new(t.path, trim: "-").render(erb_env) + rescue + warn "While rendering ERB template: #{what}" + raise + ensure + t.close + t.unlink end - - @implemented_instructions end +end - # @return [Array] List of all reachable IDL functions for the config - def implemented_functions - return @implemented_functions unless @implemented_functions.nil? - - @implemented_functions = [] - - puts " Finding all reachable functions from instruction operations" - - implemented_instructions.each do |inst| - @implemented_functions << - if inst.base.nil? - if multi_xlen? - (inst.reachable_functions(sym_table, 32) + - inst.reachable_functions(sym_table, 64)) - else - inst.reachable_functions(sym_table, mxlen) - end - else - inst.reachable_functions(sym_table, inst.base) - end - end - @implemented_functions.flatten!.uniq!(&:name) +# a synchroncous exception code +class ExceptionCode + # @return [String] Long-form display name (can include special characters) + attr_reader :name + # @return [String] Field name for an IDL enum + attr_reader :var - puts " Finding all reachable functions from CSR operations" + # @return [Integer] Code, written into *mcause + attr_reader :num - implemented_csrs.each do |csr| - csr_funcs = csr.reachable_functions(self) - csr_funcs.each do |f| - @implemented_functions << f unless @implemented_functions.any? { |i| i.name == f.name } - end - end + # @return [Extension] Extension that defines this code + attr_reader :ext - @implemented_functions + def initialize(name, var, number, ext) + @name = name + @var = var + @num = number + @ext = ext end end + +# all the same informatin as ExceptinCode, but for interrupts +InterruptCode = Class.new(ExceptionCode) diff --git a/lib/arch_obj_models/csr.rb b/lib/arch_obj_models/csr.rb index 797a3281a..823b63b42 100644 --- a/lib/arch_obj_models/csr.rb +++ b/lib/arch_obj_models/csr.rb @@ -56,7 +56,7 @@ def format_changes_with_xlen?(arch_def) end end - # @param arch_def [ImplArchDef] A configuration + # @param arch_def [ArchDef] A configuration # @return [Array] List of functions reachable from this CSR's sw_read or a field's sw_wirte function def reachable_functions(arch_def) return @reachable_functions unless @reachable_functions.nil? @@ -111,47 +111,23 @@ def reachable_functions_unevaluated(arch_def) def dynamic_length?(arch_def) return false if @data["length"].is_a?(Integer) + # when a CSR is only defined in one base, its length can't change + return false unless @data["base"].nil? + case @data["length"] when "MXLEN" - if arch_def.is_a?(ImplArchDef) - false # mxlen can never change - else - if @data["base"].nil? - # don't know MXLEN - true - else - # mxlen is always "base" - false - end - end + # mxlen can never change at runtime, so if we have it in the config, the length is not dynamic + # if we don't have it in the config, we don't know what the length is + return arch_def.mxlen.nil? when "SXLEN" - if arch_def.is_a?(ImplArchDef) - arch_def.param_values["SXLEN"] == 3264 - else - if @data["base"].nil? - # don't know SXLEN - true - else - # sxlen is always "base" - false - end - end + # dynamic if either we don't know SXLEN or SXLEN is explicitly mutable + [nil, 3264].include?(arch_def.param_values["SXLEN"]) when "VSXLEN" - if arch_def.is_a?(ImplArchDef) - arch_def.param_values["VSXLEN"] == 3264 - else - if @data["base"].nil? - # don't know VSXLEN - true - else - # vsxlen is always "base" - false - end - end + # dynamic if either we don't know VSXLEN or VSXLEN is explicitly mutable + [nil, 3264].include?(arch_def.param_values["VSXLEN"]) else raise "Unexpected length" end - # !@data["length"].is_a?(Integer) && (@data["length"] != "MXLEN") end # @param arch_def [ArchDef] Architecture definition @@ -183,63 +159,45 @@ def max_length(arch_def) # @param arch_def [ArchDef] A configuration (can be nil if the lenth is not dependent on a config parameter) # @param effective_xlen [Integer] The effective xlen, needed since some fields change location with XLEN. If the field location is not determined by XLEN, then this parameter can be nil # @return [Integer] Length, in bits, of the CSR, given effective_xlen + # @return [nil] if the length cannot be determined from the arch_def (e.g., because SXLEN is unknown and +effective_xlen+ was not provided) def length(arch_def, effective_xlen = nil) case @data["length"] when "MXLEN" - if arch_def.is_a?(ImplArchDef) - arch_def.param_values["XLEN"] - else - if !@data["base"].nil? - @data["base"] - else - # don't know MXLEN - raise ArgumentError, "for CSR #{name}: effective_xlen is required when length is MXLEN and arch_def is generic" if effective_xlen.nil? + return arch_def.mxlen unless arch_def.mxlen.nil? - effective_xlen - end + if !@data["base"].nil? + @data["base"] + else + # don't know MXLEN + effective_xlen end when "SXLEN" - if arch_def.is_a?(ImplArchDef) + if arch_def.param_values.key?("SXLEN") if arch_def.param_values["SXLEN"] == 3264 - raise ArgumentError, "effective_xlen is required when length is dynamic (#{name})" if effective_xlen.nil? - effective_xlen else - raise "CSR #{name} is not implemented" if arch_def.implemented_csrs.none? { |c| c.name == name } - raise "CSR #{name} is not implemented" if arch_def.param_values["SXLEN"].nil? - arch_def.param_values["SXLEN"] end + elsif !@data["base"].nil? + # if this CSR is only available in one base, then we know its length + @data["base"] else - if !@data["base"].nil? - @data["base"] - else - # don't know SXLEN - raise ArgumentError, "effective_xlen is required when length is SXLEN and arch_def is generic" if effective_xlen.nil? - - effective_xlen - end + # don't know SXLEN + effective_xlen end when "VSXLEN" - if arch_def.is_a?(ImplArchDef) + if arch_def.param_values.key?("VSXLEN") if arch_def.param_values["VSXLEN"] == 3264 - raise ArgumentError, "effective_xlen is required when length is dynamic (#{name})" if effective_xlen.nil? - effective_xlen else - raise "CSR #{name} is not implemented" if arch_def.param_values["VSXLEN"].nil? - arch_def.param_values["VSXLEN"] end + elsif !@data["base"].nil? + # if this CSR is only available in one base, then we know its length + @data["base"] else - if !@data["base"].nil? - @data["base"] - else - # don't know VSXLEN - raise ArgumentError, "effective_xlen is required when length is VSXLEN and arch_def is generic" if effective_xlen.nil? - - effective_xlen - end + # don't know VSXLEN + effective_xlen end when Integer @data["length"] @@ -250,37 +208,26 @@ def length(arch_def, effective_xlen = nil) # @return [Integer] The largest length of this CSR in any valid mode/xlen for the config def max_length(arch_def) + return @data["base"] unless @data["base"].nil? + case @data["length"] when "MXLEN" - if arch_def.is_a?(ImplArchDef) - arch_def.param_values["XLEN"] - else - 64 - end + arch_def.mxlen || 64 when "SXLEN" - if arch_def.is_a?(ImplArchDef) + if arch_def.param_values.key?("SXLEN") if arch_def.param_values["SXLEN"] == 3264 - raise ArgumentError, "effective_xlen is required when length is dynamic (#{name})" if effective_xlen.nil? - 64 else - raise "CSR #{name} is not implemented" if arch_def.implemented_csrs.none? { |c| c.name == name } - raise "CSR #{name} is not implemented" if arch_def.param_values["SXLEN"].nil? - arch_def.param_values["SXLEN"] end else 64 end when "VSXLEN" - if arch_def.is_a?(ImplArchDef) + if arch_def.param_values.key?("VSXLEN") if arch_def.param_values["VSXLEN"] == 3264 - raise ArgumentError, "effective_xlen is required when length is dynamic (#{name})" if effective_xlen.nil? - 64 else - raise "CSR #{name} is not implemented" if arch_def.param_values["VSXLEN"].nil? - arch_def.param_values["VSXLEN"] end else @@ -551,21 +498,19 @@ def pruned_sw_read_ast(arch_def) # @param arch_def [ArchDef] A configuration # @param effective_xlen [Integer,nil] Effective XLEN to use when CSR length is dynamic # @return [Hash] A representation of the WaveDrom drawing for the CSR (should be turned into JSON for wavedrom) - def wavedrom_desc(arch_def, effective_xlen) + def wavedrom_desc(arch_def, effective_xlen, exclude_unimplemented: false) desc = { "reg" => [] } last_idx = -1 + field_list = - if arch_def.is_a?(ImplArchDef) + if exclude_unimplemented implemented_fields_for(arch_def, effective_xlen) else - fields.select do |f| - effective_xlen.nil? || \ - ((effective_xlen == 32) && f.defined_in_base32?) || \ - ((effective_xlen == 64) && f.defined_in_base64?) - end + fields_for(effective_xlen) end + field_list.sort! { |a, b| a.location(arch_def, effective_xlen).min <=> b.location(arch_def, effective_xlen).min } field_list.each do |field| diff --git a/lib/arch_obj_models/csr_field.rb b/lib/arch_obj_models/csr_field.rb index e8b86179c..ebfa93a39 100644 --- a/lib/arch_obj_models/csr_field.rb +++ b/lib/arch_obj_models/csr_field.rb @@ -130,16 +130,18 @@ def type(arch_def) type = if @data.key?("type") @data["type"] - elsif arch_def.is_a?(ImplArchDef) + else # the type is config-specific... idl = @data["type()"] raise "type() is nil for #{csr.name}.#{name} #{@data}?" if idl.nil? + raise Idl::AstNode::ValueError.new(0, "", ""), "ArchDef is not configured" if arch_def.unconfigured? + # grab global symtab sym_table = arch_def.sym_table begin - case pruned_type_ast(sym_table.deep_clone).return_value(sym_table.deep_clone.push) + case type_checked_type_ast(sym_table.deep_clone).return_value(sym_table.deep_clone.push) when 0 "RO" when 1 @@ -160,12 +162,6 @@ def type(arch_def) warn " Return of type() function cannot be evaluated at compile time" raise e end - else - raise Idl::AstNode::ValueError.new( - type_ast(arch_def.idl_compiler).lineno, - type_ast(arch_def.idl_compiler).input_file, - "arch def is generic, can't know type exactly" - ) end @type_cache[arch_def] = type @@ -173,15 +169,11 @@ def type(arch_def) # @return [String] A pretty-printed type string def type_pretty(arch_def) - if arch_def.is_a?(ImplArchDef) + begin type(arch_def) - else - if @data.key?("type") - @data["type"] - else - ast = type_ast(arch_def.idl_compiler) - ast.gen_option_adoc - end + rescue Idl::AstNode::ValueError => e + ast = type_ast(arch_def.idl_compiler) + ast.gen_option_adoc end end @@ -213,13 +205,13 @@ def alias end # @return [Array] List of functions called thorugh this field - # @param archdef [ImplArchDef] a configuration + # @param archdef [ArchDef] a configuration # @Param effective_xlen [Integer] 32 or 64; needed because fields can change in different XLENs def reachable_functions(archdef, effective_xlen) return @reachable_functions unless @reachable_functions.nil? symtab = - if (archdef.is_a?(ImplArchDef)) + if (archdef.configured?) archdef.sym_table else raise ArgumentError, "Must supply effective_xlen for generic ArchDef" if effective_xlen.nil? @@ -292,16 +284,11 @@ def reachable_functions_unevaluated(archdef) # @return [Boolean] Whether or not the location of the field changes dynamically # (e.g., based on mstatus.SXL) in the configuration def dynamic_location?(arch_def) - if arch_def.is_a?(ImplArchDef) - unless @data["location_rv32"].nil? - csr.modes_with_access.each do |mode| - return true if arch_def.multi_xlen_in_mode?(mode) - end - end - false - else - !@data["location_rv32"].nil? - end + # if there is no location_rv32, the the field never changes + return false unless @data["location"].nil? + + # the field changes *if* some mode with access can change XLEN + csr.modes_with_access.any? { |mode| arch_def.multi_xlen_in_mode?(mode) } end # @param arch_def [IdL::Compiler] A compiler @@ -393,7 +380,7 @@ def reset_value(arch_def, effective_xlen = nil) @data["reset_value"] else symtab = - if arch_def.is_a?(ImplArchDef) + if !arch_def.mxlen.nil? arch_def.sym_table else raise ArgumentError, "effective_xlen is required when using generic arch_def" if effective_xlen.nil? @@ -406,20 +393,38 @@ def reset_value(arch_def, effective_xlen = nil) end end - def dynamic_reset_value? - @data["reset_value()"] != nil + def dynamic_reset_value?(arch_def) + return false unless @data["reset_value"].nil? + + begin + if arch_def.mxlen.nil? + # need to try with generic sym_table_32/sym_table_64 + reset_value_32 = reset_value(arch_def, 32) + reset_value_64 = reset_value(arch_def, 64) + reset_value_32 != reset_value_64 + else + # just call the function, see if we get a value error + reset_value(arch_def) + end + rescue Idl::AstNode::ValueError + true + end end def reset_value_pretty(arch_def) - if arch_def.is_a?(ImplArchDef) - reset_value(arch_def) - else - if @data.key?("reset_value") - @data["reset_value"] + begin + if arch_def.mxlen.nil? + if dynamic_reset_value?(arch_def) + raise Idl::AstNode::ValueError.new(0, "", "") + else + reset_value(arch_def, 32) # 32 or 64, doesn't matter + end else - ast = reset_value_ast(arch_def.idl_compiler) - ast.gen_option_adoc + reset_value(arch_def) end + rescue Idl::AstNode::ValueError + ast = reset_value_ast(arch_def.idl_compiler) + ast.gen_option_adoc end end @@ -490,7 +495,7 @@ def sw_write_ast(idl_compiler) # @return [Idl::FunctionBodyAst] The abstract syntax tree of the sw_write() function, type checked # @return [nil] if there is no sw_write() function # @param effective_xlen [Integer] effective xlen, needed because fields can change in different bases - # @param arch_def [ImplArchDef] A configuration + # @param arch_def [ArchDef] A configuration def pruned_sw_write_ast(arch_def, effective_xlen) @pruned_sw_write_asts ||= {} ast = @pruned_sw_write_asts[arch_def.name] @@ -498,6 +503,8 @@ def pruned_sw_write_ast(arch_def, effective_xlen) return nil unless @data.key?("sw_write(csr_value)") + raise ArgumentError, "arch_def must be configured to prune" if arch_def.unconfigured? + symtab = arch_def.sym_table.deep_clone symtab.push # all CSR instructions are 32-bit @@ -524,7 +531,7 @@ def pruned_sw_write_ast(arch_def, effective_xlen) @pruned_sw_write_asts[arch_def.name] = ast end - # @param arch_def [ArchDef] A config. May be nil if the locaiton is not configturation-dependent + # @param arch_def [ArchDef] A config. May be nil if the location is not configturation-dependent # @param effective_xlen [Integer] The effective xlen, needed since some fields change location with XLEN. If the field location is not determined by XLEN, then this parameter can be nil # @return [Range] the location within the CSR as a range (single bit fields will be a range of size 1) def location(arch_def, effective_xlen = nil) @@ -532,7 +539,7 @@ def location(arch_def, effective_xlen = nil) if @data.key?("location") "location" else - raise ArgumentError, "Expecting 32 or 64" unless [32, 64].include?(effective_xlen) + raise ArgumentError, "The location of #{csr.name}.#{name} changes with XLEN, so effective_xlen must be provided" unless [32, 64].include?(effective_xlen) "location_rv#{effective_xlen}" end @@ -540,14 +547,14 @@ def location(arch_def, effective_xlen = nil) raise "Missing location for #{csr.name}.#{name} (#{key})?" unless @data.key?(key) if @data[key].is_a?(Integer) - if (arch_def.is_a?(ImplArchDef)) - if @data[key] > csr.length(arch_def, effective_xlen || @data["base"]) - raise "Location (#{key} = #{@data[key]}) is past the csr length (#{csr.length(arch_def, effective_xlen)}) in #{csr.name}.#{name}" - end - else + csr_length = csr.length(arch_def, effective_xlen || @data["base"]) + if csr_length.nil? + # we don't know the csr length for sure, so we can only check again max_length if @data[key] > csr.max_length(arch_def) raise "Location (#{key} = #{@data[key]}) is past the max csr length (#{csr.max_length(arch_def)}) in #{csr.name}.#{name}" end + elsif @data[key] > csr_length + raise "Location (#{key} = #{@data[key]}) is past the csr length (#{csr.length(arch_def, effective_xlen)}) in #{csr.name}.#{name}" end @data[key]..@data[key] @@ -555,14 +562,14 @@ def location(arch_def, effective_xlen = nil) e, s = @data[key].split("-").map(&:to_i) raise "Invalid location" if s > e - if (arch_def.is_a?(ImplArchDef)) - if e > csr.length(arch_def, effective_xlen) - raise "Location (#{key} = #{@data[key]}) is past the csr length (#{csr.length(arch_def, effective_xlen)}) in #{csr.name}.#{name}" - end - else + csr_length = csr.length(arch_def, effective_xlen || @data["base"]) + if csr_length.nil? + # we don't know the csr length for sure, so we can only check again max_length if e > csr.max_length(arch_def) raise "Location (#{key} = #{@data[key]}) is past the max csr length (#{csr.max_length(arch_def)}) in #{csr.name}.#{name}" end + elsif e > csr_length + raise "Location (#{key} = #{@data[key]}) is past the csr length (#{csr_length}) in #{csr.name}.#{name}" end s..e @@ -644,11 +651,7 @@ def location_pretty(arch_def, effective_xlen = nil) derangeify.call(location(arch_def, effective_xlen)) end else - if arch_def.is_a?(ImplArchDef) - derangeify.call(location(arch_def, arch_def.param_values["XLEN"])) - else - derangeify.call(location(arch_def, nil)) - end + derangeify.call(location(arch_def, arch_def.mxlen)) end end diff --git a/lib/arch_obj_models/extension.rb b/lib/arch_obj_models/extension.rb index b021b28a9..a43e6a156 100644 --- a/lib/arch_obj_models/extension.rb +++ b/lib/arch_obj_models/extension.rb @@ -181,6 +181,12 @@ def implies(version_requirement = ">= 0") implications end + def conflicts + return [] if @data["conflicts"].nil? + + to_extension_requirement_list(@data["conflicts"]) + end + # @return [Array] the list of instructions implemented by this extension (may be empty) def instructions return @instructions unless @instructions.nil? diff --git a/lib/arch_obj_models/instruction.rb b/lib/arch_obj_models/instruction.rb index df31a7a8f..f07588267 100644 --- a/lib/arch_obj_models/instruction.rb +++ b/lib/arch_obj_models/instruction.rb @@ -640,7 +640,7 @@ def extension_exclusions if @data.key?("excludedBy") if @data["exludedBy"].is_a?(Array) # could be either a single extension with exclusion, or a list of exclusions - if extension_exclusion?(@data["definedBy"][0]) + if extension_exclusion?(@data["excludedBy"][0]) @extension_exclusions << to_extension_requirement(@data["excludedBy"][0]) else # this is a list diff --git a/lib/arch_obj_models/obj.rb b/lib/arch_obj_models/obj.rb index 13fe3c9f5..e27969655 100644 --- a/lib/arch_obj_models/obj.rb +++ b/lib/arch_obj_models/obj.rb @@ -119,6 +119,24 @@ def to_extension_requirement(obj) end private :to_extension_requirement + def to_extension_requirement_list(obj) + list = [] + if obj.is_a?(Array) + # could be either a single extension with exclusion, or a list of exclusions + if extension_exclusion?(obj[0]) + list << to_extension_requirement(obj[0]) + else + # this is a list + obj.each do |r| + list << to_extension_exclusion(r) + end + end + else + list << to_extension_requirement(obj) + end + list + end + def extension_requirement?(obj) obj.is_a?(String) && obj =~ /^([A-WY])|([SXZ][a-z]+)$/ || obj.is_a?(Array) && obj[0] =~ /^([A-WY])|([SXZ][a-z]+)$/ diff --git a/lib/idl/ast.rb b/lib/idl/ast.rb index b321c9e5b..c02aea01d 100644 --- a/lib/idl/ast.rb +++ b/lib/idl/ast.rb @@ -298,7 +298,7 @@ def internal_error(reason) # @param reason [String] Error message # @raise [AstNode::ValueError] always def value_error(reason) - raise AstNode::ValueError.new(lineno, input_file, reason) + raise AstNode::ValueError.new(lineno, input_file, reason), reason, [] end # unindent a multiline string, getting rid of all common leading whitespace (like <<~ heredocs) @@ -701,11 +701,13 @@ def initialize(input, interval, expression) end def type_check(symtab) - type_error "#{expression.text_value} is not an array" unless expression.type(symtab).kind == :array - if symtab.archdef.is_a?(ImplArchDef) - type_error "#{expression.text_value} must have a known value at compile time" if expression.type(symtab).width == :unknown - else - type_error "#{expression.text_value} must be a constant" unless expression.type(symtab).const? + expression.type_check(symtab) + expression_type = expression.type(symtab) + type_error "#{expression.text_value} is not an array" unless expression_type.kind == :array + type_error "#{expression.text_value} must be a constant" unless expression_type.const? + + if symtab.archdef.fully_configured? && (expression_type.width == :unknown) + type_error "#{expression.text_value} must have a known value at compile time" end end @@ -966,9 +968,38 @@ def type_check(_symtab) end end + def element_names(symtab) + case name + when 'ExtensionName' + symtab.archdef.extensions.map(&:name) + when 'ExceptionCode' + symtab.archdef.exception_codes(&:var) + when 'InterruptCode' + symtab.archdef.interrupt_codes(&:var) + else + type_error "Unknown builtin enum type '#{name}'" + end + end + + def element_values(symtab) + case name + when 'ExtensionName' + (0...symtab.archdef.extensions.size).to_a + when 'ExceptionCode' + symtab.archdef.exception_codes(&:num) + when 'InterruptCode' + symtab.archdef.interrupt_codes(&:num) + else + type_error "Unknown builtin enum type '#{name}'" + end + end + # @!macro type_no_archdef def type(symtab) = symtab.get(@user_type.text_value) + # @return [String] name of the enum class + def name = @user_type.text_value + # @!macro to_idl def to_idl = "builtin enum #{@user_type.text_value}" end @@ -1064,6 +1095,10 @@ def initialize(input, interval, name, size, fields) @size = size @fields = fields end + + def size(symtab) + @size.value(symtab) + end # @return [Array] Array of all element names, in the same order as those from {#element_ranges} def element_names @@ -2018,7 +2053,7 @@ def decl_type(symtab) begin dtype = Type.new(:array, width: ary_size.value(symtab), sub_type: dtype.clone, qualifiers:) rescue ValueError - type_error "Array size must be known at compile time" if symtab.archdef.is_a?(ImplArchDef) + type_error "Array size must be known at compile time" if symtab.archdef.fully_configured? dtype = Type.new(:array, width: :unknown, sub_type: dtype.clone, qualifiers:) end end @@ -2042,9 +2077,8 @@ def type_check(symtab) begin ary_size.value(symtab) rescue ValueError - # if this is an ImplArchDef, this is an error because all constants are supposed to be known - if symtab.archdef.is_a?(ImplArchDef) - puts symtab.get(ary_size.text_value) + # if this is a fully configured ArchDef, this is an error because all constants are supposed to be known + if symtab.archdef.fully_configured? type_error "Array size (#{ary_size.text_value}) must be known at compile time" else # otherwise, it's ok that we don't know the value yet, as long as the value is a const @@ -2378,9 +2412,9 @@ def invert(symtab) } if inverted_op_map.key?(op) - BinaryExpressionAst.new(input, interval, lhs, inverted_op_map[op], rhs) + BinaryExpressionAst.new(input, interval, lhs.dup, inverted_op_map[op], rhs.dup) else - UnaryOperatorExpressionAst.new(input, interval, "!", self) + UnaryOperatorExpressionAst.new(input, interval, "!", self.dup) end # else # # harder case of && / || @@ -3838,7 +3872,7 @@ def type_check(symtab) begin type_error "Bits width (#{bits_expression.value(symtab)}) must be positive" unless bits_expression.value(symtab).positive? rescue ValueError - type_error "Bit width must be known at compile time" if symtab.is_a?(ImplArchDef) + type_error "Bit width must be known at compile time" if symtab.archdef.fully_configured? end end unless ["Bits", "String", "XReg", "Boolean", "U32", "U64"].include?(@type_name) @@ -4246,9 +4280,9 @@ def value(symtab) extname_ref = arg_nodes[0] type_error "First argument should be a ExtensionName" unless extname_ref.type(symtab).kind == :enum_ref && extname_ref.class_name == "ExtensionName" - return symtab.archdef.ext?(arg_nodes[0].member_name) if symtab.archdef.is_a?(ImplArchDef) + return symtab.archdef.ext?(arg_nodes[0].member_name) if symtab.archdef.fully_configured? - value_error "implemented? is only known when evaluating in the context of a configuration" + value_error "implemented? is only known when evaluating in the context of a fully-configured arch def" else value_error "value of builtin function cannot be known" end @@ -5344,7 +5378,7 @@ def csr_name(symtab) def value(symtab) cd = csr_def(symtab) value_error "CSR number not knowable" if cd.nil? - if symtab.archdef.is_a?(ImplArchDef) + if symtab.archdef.fully_configured? value_error "CSR is not implemented" unless symtab.archdef.implemented_csrs.any? { |icsr| icsr.name == cd.name } else value_error "CSR is not defined" unless symtab.archdef.csrs.any? { |icsr| icsr.name == cd.name } diff --git a/lib/idl/symbol_table.rb b/lib/idl/symbol_table.rb index f1097520d..1a7ee03d1 100644 --- a/lib/idl/symbol_table.rb +++ b/lib/idl/symbol_table.rb @@ -91,8 +91,8 @@ def hash def initialize(arch_def, effective_xlen = nil) @archdef = arch_def - if arch_def.is_a?(ImplArchDef) - raise "effective_xlen should not be set when symbol table is given an ImplArchDef" unless effective_xlen.nil? + if arch_def.fully_configured? + raise "effective_xlen should not be set when symbol table is given a fully-configured ArchDef" unless effective_xlen.nil? else raise "effective_xlen should be set when symbol table is given an ArchDef" if effective_xlen.nil? end @@ -116,28 +116,24 @@ def initialize(arch_def, effective_xlen = nil) ) }] - if arch_def.is_a?(ImplArchDef) - arch_def.params_with_value.each do |param_with_value| - type = Type.from_json_schema(param_with_value.schema).make_const - if type.kind == :array && type.width == :unknown - type = Type.new(:array, width: param_with_value.value.length, sub_type: type.sub_type) - end + arch_def.params_with_value.each do |param_with_value| + type = Type.from_json_schema(param_with_value.schema).make_const + if type.kind == :array && type.width == :unknown + type = Type.new(:array, width: param_with_value.value.length, sub_type: type.sub_type) + end - # could already be present... - existing_sym = get(param_with_value.name) - if existing_sym.nil? - add!(param_with_value.name, Var.new(param_with_value.name, type, param_with_value.value)) - else - unless existing_sym.type.equal_to?(type) && existing_sym.value == param_with_value.value - raise DuplicateSymError, "Definition error: Param #{param.name} is defined by multiple extensions and is not the same definition in each" - end + # could already be present... + existing_sym = get(param_with_value.name) + if existing_sym.nil? + add!(param_with_value.name, Var.new(param_with_value.name, type, param_with_value.value)) + else + unless existing_sym.type.equal_to?(type) && existing_sym.value == param_with_value.value + raise DuplicateSymError, "Definition error: Param #{param.name} is defined by multiple extensions and is not the same definition in each" end end end # now add all parameters, even those not implemented - arch_def.params.each do |param| - next if arch_def.is_a?(ImplArchDef) && arch_def.params_with_value.any? { |p| p.name == param.name } - + arch_def.params_without_value.each do |param| if param.exts.size == 1 if param.name == "XLEN" # special case: we actually do know XLEN diff --git a/lib/idl/tests/helpers.rb b/lib/idl/tests/helpers.rb index e69ee73cd..02dcfb229 100644 --- a/lib/idl/tests/helpers.rb +++ b/lib/idl/tests/helpers.rb @@ -22,9 +22,6 @@ def exception_codes = [OpenStruct.new(var: "ACode", num: 0), OpenStruct.new(var: def interrupt_codes = [OpenStruct.new(var: "CoolInterrupt", num: 1)] end -class ImplArchDef -end - module TestMixin def setup @archdef = MockArchDef.new diff --git a/lib/validate.rb b/lib/validate.rb index 421935d2b..3de4f12d0 100644 --- a/lib/validate.rb +++ b/lib/validate.rb @@ -1,5 +1,6 @@ # frozen_string_literal: true +require "date" require "json" require "json_schemer" require "pathname" @@ -132,7 +133,7 @@ def validate_str(str, type: nil, schema_path: nil) raise "Invalid type #{type}" unless TYPES.any?(type) || !schema_path.nil? begin - obj = YAML.safe_load(str, permitted_classes: [Symbol]) + obj = YAML.safe_load(str, permitted_classes: [Symbol, Date]) rescue Psych::SyntaxError => e warn "While parsing: #{str}\n\n" raise e diff --git a/schemas/arch_schema.json b/schemas/arch_schema.json index 63de02d59..56da6cccb 100644 --- a/schemas/arch_schema.json +++ b/schemas/arch_schema.json @@ -4,15 +4,34 @@ "type": "object", "title": "Unified Architecture Specification", "required": [ + "type", "extensions", - "implemented_extensions", "csrs", - "implemented_csrs", - "params", - "instructions", - "implemented_instructions" + "instructions" + ], + "allOf": [ + { + "if": { + "properties": { + "type": { "enum": ["partially configured", "fully configured"]} + } + }, + "then": { + "required": [ + "implemented_instructions", + "implemented_extensions", + "implemented_csrs", + "params" + ] + } + } ], "properties": { + "type": { + "type": "string", + "description": "Type of the arch", + "enum": ["unconfigured", "partially configured", "fully configured"] + }, "params": { "type": "object" }, @@ -85,6 +104,21 @@ "pattern": "^[a-z][a-zA-Z0-9.]+$", "description": "Instruction name" } + }, + "profile_families": { + "type": "object" + }, + "profiles": { + "type": "object" + }, + "manuals": { + "type": "object" + }, + "csc_crd_families": { + "type": "object" + }, + "csc_crds": { + "type": "object" } }, "additionalProperties": false diff --git a/schemas/csr_schema.json b/schemas/csr_schema.json index 74c61fb85..d6265c809 100644 --- a/schemas/csr_schema.json +++ b/schemas/csr_schema.json @@ -15,25 +15,16 @@ "description": "Name of the field. Optional because it is implied by the object key of the CSR object holding the field" }, "location": { - "oneOf": [ - {"type": "number", "description": "Location of a single bit"}, - {"type": "string", "pattern": "^[0-9]+-[0-9]+$", "description": "Location range of a multibit field"} - ], + "$ref": "schema_defs.json#/$defs/field_location", "description": "Location of the field within the CSR register" }, "location_rv32": { - "description": "Location of the field within the CSR register when the effective XLEN of the current mode is 32", - "oneOf": [ - {"type": "number", "description": "Location of a single bit"}, - {"type": "string", "pattern": "^[0-9]+-[0-9]+$", "description": "Location range of a multibit field"} - ] + "$ref": "schema_defs.json#/$defs/field_location", + "description": "Location of the field within the CSR register when the effective XLEN of the current mode is 32" }, "location_rv64": { - "description": "Location of the field within the CSR register when the effective XLEN of the current mode is 64", - "oneOf": [ - {"type": "number", "description": "Location of a single bit"}, - {"type": "string", "pattern": "^[0-9]+-[0-9]+$", "description": "Location range of a multibit field"} - ] + "$ref": "schema_defs.json#/$defs/field_location", + "description": "Location of the field within the CSR register when the effective XLEN of the current mode is 64" }, "reset_value": { "description": "Value of the state after reset. Can be UNDEFINED_LEGAL for the generic architecture spec, but must have an integer value for the implementation spec", diff --git a/schemas/ext_schema.json b/schemas/ext_schema.json index d1071745e..a95d4783e 100644 --- a/schemas/ext_schema.json +++ b/schemas/ext_schema.json @@ -106,6 +106,13 @@ "additionalProperties": false }, "type": { "enum": ["unprivileged", "privileged"] }, + "conflicts": { + "description": "Extension(s) that conflict with this extension; both cannot be implemented at the same time", + "oneOf": [ + { "$ref": "schema_defs.json#/$defs/extension_requirement" }, + { "type": "array", "items": { "$ref": "schema_defs.json#/$defs/extension_requirement" }} + ] + }, "versions": { "type": "array", "items": { @@ -163,13 +170,6 @@ "description": "Extension(s) required by this extension", "$ref": "#/$defs/requires_entry" }, - "conflicts": { - "description": "Extension(s) that conflict with this extension; both cannot be implemented at the same time", - "oneOf": [ - { "$ref": "schema_defs.json#/$defs/extension_requirement" }, - { "type": "array", "items": { "$ref": "schema_defs.json#/$defs/extension_requirement" }} - ] - }, "contributors": { "description": "List of contributors to this version of the extension", "type": "array", @@ -276,6 +276,10 @@ } }, "additionalProperties": false + }, + "__source": { + "type": "string", + "description": "Source file where this extension was defined" } }, "additionalProperties": false diff --git a/schemas/schema_defs.json b/schemas/schema_defs.json index 40221c8ab..b6f1c7aa4 100644 --- a/schemas/schema_defs.json +++ b/schemas/schema_defs.json @@ -8,6 +8,13 @@ "type": "string", "pattern": "^(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)(?:-((?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\\.(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\\+([0-9a-zA-Z-]+(?:\\.[0-9a-zA-Z-]+)*))?$" }, + "field_location": { + "oneOf": [ + {"type": "number", "description": "Location of a single bit"}, + {"type": "string", "pattern": "^[0-9]+-[0-9]+$", "description": "Location range of a multibit field"} + ], + "description": "Location of a field in a register" + }, "spec_state": { "type": "string", "enum": [ From 85ea0fe169e80d053da1b53d847ff8527a07d175 Mon Sep 17 00:00:00 2001 From: Derek Hower Date: Thu, 3 Oct 2024 06:46:54 -0700 Subject: [PATCH 4/9] Add missing mock methods for IDL test --- lib/idl/tests/helpers.rb | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/lib/idl/tests/helpers.rb b/lib/idl/tests/helpers.rb index 02dcfb229..0f293e6b2 100644 --- a/lib/idl/tests/helpers.rb +++ b/lib/idl/tests/helpers.rb @@ -11,15 +11,22 @@ def initialize(name) end MockExtensionParameter = Struct.new(:name, :desc, :schema, :extra_validation, :exts, :type) +MockExtensionParameterWithValue = Struct.new(:name, :desc, :schema, :extra_validation, :exts, :value) # ArchDef mock that knows about XLEN and extensions class MockArchDef def param_values = { "XLEN" => 32 } + def params_with_value = [MockExtensionParameterWithValue.new("XLEN", "mxlen", {"type" => "integer", "enum" => [32, 64]}, nil, nil, 32)] + def params_without_value = [] def params = [] def extensions = [MockExtension.new("I")] def mxlen = 64 def exception_codes = [OpenStruct.new(var: "ACode", num: 0), OpenStruct.new(var: "BCode", num: 1)] def interrupt_codes = [OpenStruct.new(var: "CoolInterrupt", num: 1)] + + def fully_configured? = false + def partially_configured? = true + def unconfigured? = false end module TestMixin From 67d9fc07b685610646f8dfcd76415337e05e91fc Mon Sep 17 00:00:00 2001 From: Derek Hower Date: Thu, 3 Oct 2024 13:26:00 -0700 Subject: [PATCH 5/9] Add some more parameters to reflects options in mstatus --- arch/csr/mstatus.yaml | 74 +++++++++++++++++++++++++++++++++++++------ arch/ext/F.yaml | 15 ++++++++- arch/ext/S.yaml | 31 +++++++++++++++++- lib/idl/type.rb | 12 ++++--- 4 files changed, 116 insertions(+), 16 deletions(-) diff --git a/arch/csr/mstatus.yaml b/arch/csr/mstatus.yaml index 98701bec1..ee965c31f 100644 --- a/arch/csr/mstatus.yaml +++ b/arch/csr/mstatus.yaml @@ -241,9 +241,29 @@ mstatus: *`hfence.vvma`, `sfence.w.inval`, or `sfence.inval.ir` to trap. * Any additional traps in VS-mode (controlled via `hstatus.VTVM` instead). - type: RW + type(): | + if (CSR[misa].S == 1'b0) { + return CsrFieldType::RO; + } else { + return CsrFieldType::RW; + } definedBy: S - reset_value: UNDEFINED_LEGAL + reset_value(): | + if (CSR[misa].S == 1'b0) { + return 0; + } else if (MSTATUS_TVM_IMPLEMENTED) { + return UNDEFINED_LEGAL; + } else { + return 0; + } + sw_write(csr_value): | + if (CSR[misa].S == 1'b0) { + return 0; + } else if (MSTATUS_TVM_IMPLEMENTED) { + return csr_value.TVM; + } else { + return 0; + } MXR: location: 19 description: | @@ -286,7 +306,8 @@ mstatus: `mstatus.MPRV` is cleared on any exception return (`mret` or `sret` instruction, regardless of the trap handler privilege mode). definedBy: U - type: RW-H + type(): | + return (CSR[misa].U == 1'b1) ? CsrFieldType::RWH : CsrFieldType::RO; reset_value: 0 XS: location: 16-15 @@ -308,9 +329,40 @@ mstatus: When a floating point register, or the fCSR register is written, FS obtains the value 3. Values 1 and 2 are valid write values for software, but are not interpreted by hardware other than to possibly enable a previously-disabled floating point unit. - type: RW-H + type(): | + if (CSR[misa].F == 1'b1){ + return CsrFieldType::RWH; + } else if ((CSR[misa].S == 1'b0) && (CSR[misa].F == 1'b0)) { + # must be read-only-0 + return CsrFieldType::RO; + } else { + assert (CSR[misa].S == 1'b1); + # there will be no hardware update in this case because we know the F extension isn't implemented + return MSTATUS_FS_WRITEABLE ? CsrFieldType::RW : CsrFieldType::RO; + } definedBy: F - reset_value: UNDEFINED_LEGAL + reset_value(): | + if (CSR[misa].F == 1'b1){ + return UNDEFINED_LEGAL; + } else if ((CSR[misa].S == 1'b0) && (CSR[misa].F == 1'b0)) { + # must be read-only-0 + return 0; + } else { + assert (CSR[misa].S == 1'b1); + # there will be no hardware update in this case because we know the F extension isn't implemented + return MSTATUS_FS_WRITEABLE ? UNDEFINED_LEGAL : 0; + } + sw_write(csr_value): | + if (CSR[misa].F == 1'b1){ + return ary_includes?(MSTATUS_FS_LEGAL_VALUES, csr_value.FS) ? csr_value.FS : UNDEFINED_LEGAL_DETERMINISTIC; + } else if ((CSR[misa].S == 1'b0) && (CSR[misa].F == 1'b0)) { + # must be read-only-0 + return 0; + } else { + assert (CSR[misa].S == 1'b1); + # there will be no hardware update in this case because we know the F extension isn't implemented + return ary_includes?(MSTATUS_FS_LEGAL_VALUES, csr_value.FS) ? csr_value.FS : UNDEFINED_LEGAL_DETERMINISTIC; + } MPP: location: 12-11 description: | @@ -449,9 +501,11 @@ mstatus: Can also be written by software without immediate side effect. Other than serving as a record of nested traps as described above, `mstatus.SPIE` does not affect execution. - type: RW-H + type(): | + return (CSR[misa].S == 1'b1) ? CsrFieldType::RWH : CsrFieldType::RO; definedBy: S - reset_value: UNDEFINED_LEGAL + reset_value(): | + return (CSR[misa].S == 1'b1) ? UNDEFINED_LEGAL : 0; MIE: location: 3 description: | @@ -484,6 +538,8 @@ mstatus: * When 0, all (H)S-mode interrupts are disabled when the current privilege level is (H)S (M-mode interrupts are still enabled). * When 1, (H)S-mode interrupts that are not otherwise disabled with a field in `sie` are enabled. - type: RW-H + type(): | + return (CSR[misa].S == 1'b1) ? CsrFieldType::RWH : CsrFieldType::RO; definedBy: S - reset_value: UNDEFINED_LEGAL + reset_value(): | + return (CSR[misa].S == 1'b1) ? UNDEFINED_LEGAL : 0; diff --git a/arch/ext/F.yaml b/arch/ext/F.yaml index f88bed272..9586d500e 100644 --- a/arch/ext/F.yaml +++ b/arch/ext/F.yaml @@ -238,4 +238,17 @@ F: description: | Indicates whether or not the `F` extension can be disabled with the `misa.F` bit. schema: - type: boolean \ No newline at end of file + type: boolean + MSTATUS_FS_LEGAL_VALUES: + description: | + The set of values that mstatus.FS will accept from a software write. + schema: + type: array + items: + type: integer + enum: [0,1,2,3] + maxItems: 4 + uniqueItems: true + also_defined_in: S + extra_validation: | + assert MSTATUS_FS_LEGAL_VALUES.include?(0) && MSTATUS_FS_LEGAL_VALUES.include?(3) if ext?(:F) \ No newline at end of file diff --git a/arch/ext/S.yaml b/arch/ext/S.yaml index c6b5d50ce..31994d9e6 100644 --- a/arch/ext/S.yaml +++ b/arch/ext/S.yaml @@ -242,4 +242,33 @@ S: type: boolean default: false extra_validation: - assert TRAP_ON_SFENCE_VMA_WHEN_SATP_MODE_IS_READ_ONLY == false if ext?(:Sv32) || ext?(:Sv39) || ext?(:Sv48) || ext?(:Sv57) \ No newline at end of file + assert TRAP_ON_SFENCE_VMA_WHEN_SATP_MODE_IS_READ_ONLY == false if ext?(:Sv32) || ext?(:Sv39) || ext?(:Sv48) || ext?(:Sv57) + MSTATUS_FS_WRITEABLE: + description: | + When `S` is enabled but `F` is not, mstatus.FS is optionally writeable. + + This parameter only has an effect when both S and F mode are disabled. + schema: + type: boolean + extra_validation: + assert MSTATUS_FS_WRITEABL == true if ext?(:F) + MSTATUS_FS_LEGAL_VALUES: + description: | + The set of values that mstatus.FS will accept from a software write. + schema: + type: array + items: + type: integer + enum: [0,1,2,3] + maxItems: 4 + uniqueItems: true + also_defined_in: F + extra_validation: | + assert MSTATUS_FS_LEGAL_VALUES.include?(0) && MSTATUS_FS_LEGAL_VALUES.include?(3) if ext?(:F) + MSTATUS_TVM_IMPLEMENTED: + description: | + Whether or not mstatus.TVM is implemented. + + When not implemented mstatus.TVM will be read-only-zero. + schema: + type: boolean diff --git a/lib/idl/type.rb b/lib/idl/type.rb index 46c2dafd3..14dc9dfd3 100644 --- a/lib/idl/type.rb +++ b/lib/idl/type.rb @@ -172,15 +172,17 @@ def equal_to?(type) case @kind when :boolean - return type.kind == :boolean + type.kind == :boolean when :enum_ref - return type.kind == :enum_ref && type.name == @enum_class.name + type.kind == :enum_ref && type.name == @enum_class.name when :dontcare - return true + true when :bits - return type.kind == :bits && type.width == @width + type.kind == :bits && type.width == @width when :string - return type.kind == :string && type.width == @width + type.kind == :string && type.width == @width + when :array + type.kind == :array && type.sub_type.equal_to?(@sub_type) else raise "unimplemented type '#{@kind}'" end From e416e55788b869ec306186f2cb9869aa840f6d3c Mon Sep 17 00:00:00 2001 From: Derek Hower Date: Thu, 3 Oct 2024 13:28:47 -0700 Subject: [PATCH 6/9] Fix spelling error --- backends/cfg_html_doc/templates/csr.adoc.erb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/backends/cfg_html_doc/templates/csr.adoc.erb b/backends/cfg_html_doc/templates/csr.adoc.erb index 0ceb96ce4..ed264767d 100644 --- a/backends/cfg_html_doc/templates/csr.adoc.erb +++ b/backends/cfg_html_doc/templates/csr.adoc.erb @@ -29,7 +29,7 @@ h| Privilege Mode | <%= csr.priv_mode %> .<%= csr.name %> format [wavedrom, ,svg,subs='attributes',width="100%"] .... -<%= JSON.dump csr.wavedrom_desc(arch_def, arch_def.param_values["XLEN"], exclude_unimplmented: true) %> +<%= JSON.dump csr.wavedrom_desc(arch_def, arch_def.param_values["XLEN"], exclude_unimplemented: true) %> .... <%- else -%> <%# CSR has a dynamic length, or a field has a dynamic location, @@ -39,13 +39,13 @@ This CSR format changes dynamically. .<%= csr.name %> Format when <%= csr.length_cond32 %> [wavedrom, ,svg,subs='attributes',width="100%"] .... -<%= JSON.dump csr.wavedrom_desc(arch_def, 32, exclude_unimplmented: true) %> +<%= JSON.dump csr.wavedrom_desc(arch_def, 32, exclude_unimplemented: true) %> .... .<%= csr.name %> Format when <%= csr.length_cond64 %> [wavedrom, ,svg,subs='attributes',width="100%"] .... -<%= JSON.dump csr.wavedrom_desc(arch_def, 64, exclude_unimplmented: true) %> +<%= JSON.dump csr.wavedrom_desc(arch_def, 64, exclude_unimplemented: true) %> .... From 182fbf1625ded72d7c7c1e14eb689cf29b987245 Mon Sep 17 00:00:00 2001 From: Derek Hower Date: Thu, 3 Oct 2024 13:40:35 -0700 Subject: [PATCH 7/9] Handle case where CSR field type isn't known from an unconfiguuredarch def --- arch/csr/mstatus.yaml | 7 ++----- lib/idl/ast.rb | 8 ++++++-- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/arch/csr/mstatus.yaml b/arch/csr/mstatus.yaml index ee965c31f..3ffadc08e 100644 --- a/arch/csr/mstatus.yaml +++ b/arch/csr/mstatus.yaml @@ -336,7 +336,6 @@ mstatus: # must be read-only-0 return CsrFieldType::RO; } else { - assert (CSR[misa].S == 1'b1); # there will be no hardware update in this case because we know the F extension isn't implemented return MSTATUS_FS_WRITEABLE ? CsrFieldType::RW : CsrFieldType::RO; } @@ -348,20 +347,18 @@ mstatus: # must be read-only-0 return 0; } else { - assert (CSR[misa].S == 1'b1); # there will be no hardware update in this case because we know the F extension isn't implemented return MSTATUS_FS_WRITEABLE ? UNDEFINED_LEGAL : 0; } sw_write(csr_value): | if (CSR[misa].F == 1'b1){ - return ary_includes?(MSTATUS_FS_LEGAL_VALUES, csr_value.FS) ? csr_value.FS : UNDEFINED_LEGAL_DETERMINISTIC; + return ary_includes?<$array_size(MSTATUS_FS_LEGAL_VALUES), 2>(MSTATUS_FS_LEGAL_VALUES, csr_value.FS) ? csr_value.FS : UNDEFINED_LEGAL_DETERMINISTIC; } else if ((CSR[misa].S == 1'b0) && (CSR[misa].F == 1'b0)) { # must be read-only-0 return 0; } else { - assert (CSR[misa].S == 1'b1); # there will be no hardware update in this case because we know the F extension isn't implemented - return ary_includes?(MSTATUS_FS_LEGAL_VALUES, csr_value.FS) ? csr_value.FS : UNDEFINED_LEGAL_DETERMINISTIC; + return ary_includes?<$array_size(MSTATUS_FS_LEGAL_VALUES), 2>(MSTATUS_FS_LEGAL_VALUES, csr_value.FS) ? csr_value.FS : UNDEFINED_LEGAL_DETERMINISTIC; } MPP: location: 12-11 diff --git a/lib/idl/ast.rb b/lib/idl/ast.rb index c02aea01d..dc3c653b8 100644 --- a/lib/idl/ast.rb +++ b/lib/idl/ast.rb @@ -1831,8 +1831,12 @@ def field(symtab) def type_check(symtab) csr_field.type_check(symtab) - if ["RO", "RO-H"].any?(csr_field.field_def(symtab).type(symtab.archdef)) - type_error "Cannot write to read-only CSR field" + begin + if ["RO", "RO-H"].any?(csr_field.field_def(symtab).type(symtab.archdef)) + type_error "Cannot write to read-only CSR field" + end + rescue ValueError + # ok, we don't know the type because the archdef isn't configured end write_value.type_check(symtab) From f09c67483de3d465e8aa9baf2f79d279796810a3 Mon Sep 17 00:00:00 2001 From: Derek Hower Date: Thu, 3 Oct 2024 13:45:06 -0700 Subject: [PATCH 8/9] Add new parameters to generic_rv64 params --- cfgs/generic_rv64/params.yaml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/cfgs/generic_rv64/params.yaml b/cfgs/generic_rv64/params.yaml index 9ae25389d..68a209e30 100644 --- a/cfgs/generic_rv64/params.yaml +++ b/cfgs/generic_rv64/params.yaml @@ -505,4 +505,6 @@ params: TINST_VALUE_ON_LOAD_PAGE_FAULT: "always zero" TINST_VALUE_ON_STORE_AMO_PAGE_FAULT: "always zero" MTVEC_MODES: [0, 1] - + MSTATUS_FS_LEGAL_VALUES: [0,1,2,3] + MSTATUS_FS_WRITEABLE: true + MSTATUS_TVM_IMPLEMENTED: true From e2d8f4ec7d5f57e212b475036fcfbe6c7c91723a Mon Sep 17 00:00:00 2001 From: dhower-qc <134728312+dhower-qc@users.noreply.github.com> Date: Thu, 3 Oct 2024 17:33:03 -0400 Subject: [PATCH 9/9] Fix spelling error --- arch/ext/S.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/ext/S.yaml b/arch/ext/S.yaml index 31994d9e6..3934d993a 100644 --- a/arch/ext/S.yaml +++ b/arch/ext/S.yaml @@ -251,7 +251,7 @@ S: schema: type: boolean extra_validation: - assert MSTATUS_FS_WRITEABL == true if ext?(:F) + assert MSTATUS_FS_WRITEABLE == true if ext?(:F) MSTATUS_FS_LEGAL_VALUES: description: | The set of values that mstatus.FS will accept from a software write.