diff --git a/.github/workflows/pages.yml b/.github/workflows/pages.yml index 74fe11ae21..3afb0a977f 100644 --- a/.github/workflows/pages.yml +++ b/.github/workflows/pages.yml @@ -58,7 +58,7 @@ jobs: - name: Copy cfg html run: cp -R gen/cfg_html_doc/generic_rv64/html _site/example_cfg - name: Create RVA Family PDF Spec - run: ./do gen:profile_pdf[rva] + run: ./do gen:profile[rva] - name: Copy RVA Family PDF run: cp gen/profile_doc/pdf/rva.pdf _site/pdfs/rva.pdf - name: Create MC-1 PDF Spec diff --git a/.gitignore b/.gitignore index e82076dd84..1d7cd45ac1 100644 --- a/.gitignore +++ b/.gitignore @@ -10,6 +10,7 @@ diag-ditaa-* arch/manual/isa/**/riscv-isa-manual gen +gen_expected node_modules _site images diff --git a/Rakefile b/Rakefile index f63de6bcfc..8c6a9cab17 100644 --- a/Rakefile +++ b/Rakefile @@ -289,7 +289,8 @@ task :regress do Rake::Task["gen:html"].invoke("generic_rv64") Rake::Task["gen:crd_pdf"].invoke("MockCRD-1") Rake::Task["gen:crd_pdf"].invoke("MC-1") - Rake::Task["gen:profile_pdf"].invoke("rva") + Rake::Task["gen:profile"].invoke("MockProfileFamily") + Rake::Task["gen:profile"].invoke("rva") puts puts "Regression test PASSED" diff --git a/arch/crd/MC-1.yaml b/arch/crd/MC-1.yaml index c7d3d9980d..f7a5e15f40 100644 --- a/arch/crd/MC-1.yaml +++ b/arch/crd/MC-1.yaml @@ -8,6 +8,59 @@ MC-1: # semantic version within the CRD family version: "1.0" + revision_history: + - revision: "0.7" + date: 2024-07-29 + changes: + - First version after moving non-microcontroller content in this document to a new document + called “RISC-V CRDs (Certification Requirement Documents)” + - Change MC-1 Unpriv ISA spec from + “https://riscv.org/wp-content/uploads/2016/06/riscv-spec-v2.1.pdf[riscv-spec-v2.1], May 31, + 2016” to https://github.com/riscv/riscv-isa-manual/releases/tag/Ratified-IMAFDQC since the + former isn't ratified by the latter is the oldest ratified version. + - Added requirements for WFI instruction + - Added requirements related to msip memory-mapped register + - revision: "0.6" + date: 2024-07-11 + changes: + - Supporting multiple MC versions to support customers wanting to certify existing microcontrollers not using the latest version of ratified standards. + - Changed versioning scheme to use major.minor.patch instead of 3-digit major & minor. + - Added a table showing the mapping from MC version to ISA manuals. + - Reluctantly made interrupts OUT OF SCOPE for MC-1 since only the CLINT interrupt controller + was ratified at that time and isn’t anticipated to be the interrupt controller used by MC-1 implementations. + - Clarified MANDATORY behaviors for mie and mip CSRs + - Removed canonical discovery recipe because the OPT-* options directly inform the certification + tests and certification reference model of the status of the various options. Also, canonical + discovery recipes (e.g., probing for CLIC) violate the certification approach of avoiding writing + potentially illegal values to CSR fields. + - Added more options for interrupts + - Moved non-microcontroller content in this document to a new document called “RISC-V Certification Plans” + - revision: "0.5" + date: 2024-06-03 + changes: + - Renamed to “RISC-V Microcontroller Certification Plan” based on Jason’s recommendation + - Added mvendorid, marchid, mimpid, and mhardid read-only priv CSRs because Allen pointed out + these are mandatory in M-mode v1.13 (probably older versions too, haven’t looked yet). + - Added table showing mapping of MC versions to associated RISC-V specifications + - revision: "0.4" + date: 2024-06-03 + changes: + - Added M-mode instruction requirements + - Made Zicntr MANDATORY due to very low cost for implementations to support (in the spirit of minimizing options). + - Removed OPT-CNTR-PREC since minstret and mcycle must be a full 64 bits to be standard-compliant. + - revision: "0.3" + date: 2024-05-25 + changes: + - Includes Zicntr as OPTIONAL and then has only 32-bit counters for instret and cycle. + - revision: "0.2" + date: 2024-05-20 + changes: + - Very early draft + - revision: "0.1" + date: 2024-05-16 + changes: + - Initial version + # XLEN used by rakefile base: 32 diff --git a/arch/crd/MockCRD-1.yaml b/arch/crd/MockCRD-1.yaml index 3d70427125..16ad4c58e7 100644 --- a/arch/crd/MockCRD-1.yaml +++ b/arch/crd/MockCRD-1.yaml @@ -12,6 +12,16 @@ MockCRD-1: # semantic version within the CRD family version: "1.0" + revision_history: + - revision: "0.1" + date: 2024-10-04 + changes: + - Created to test CRDs + - revision: "0.2" + date: 2024-10-05 + changes: + - Also created to test CRDs + description: | Mock CRD description: diff --git a/arch/crd_family/MockCRDFamily.yaml b/arch/crd_family/MockCRDFamily.yaml index d2a2bdeca7..574329c0ed 100644 --- a/arch/crd_family/MockCRDFamily.yaml +++ b/arch/crd_family/MockCRDFamily.yaml @@ -1,15 +1,6 @@ MockCRDFamily: name: MockCRDFamily long_name: Mock CRD Family Long Name - revision_history: - - version: "0.1" - date: 2024-10-04 - changes: - - Created to test CRDs - - version: "0.2" - date: 2024-10-05 - changes: - - Also created to test CRDs introduction: | Here's the Mock CRD Family's introduction. @@ -18,4 +9,6 @@ MockCRDFamily: Here's the Mock CRD Family's naming scheme. mandatory_priv_modes: - - M \ No newline at end of file + - M + + description: Here's the Mock CRD Family's description. \ No newline at end of file diff --git a/arch/profile/MockProfile-1.yaml b/arch/profile/MockProfile-1.yaml new file mode 100644 index 0000000000..417542d3fa --- /dev/null +++ b/arch/profile/MockProfile-1.yaml @@ -0,0 +1,28 @@ +MockProfile-1: + family: MockProfileFamily + description: This is the Mock Profile description. + marketing_name: MockProfile-1 Marketing Name + mode: S + version: "1.0" + contributors: + - name: Krste Asanovic + email: krste@sifive.com + company: SiFive + extensions: + - name: S + presence: mandatory + version: "= 1.11" + - name: Zifencei + presence: mandatory + version: "= 2.0" + note: | + Zifencei is mandated as it is the only standard way to support + instruction-cache coherence in RVA20 application processors. A new + instruction-cache coherence mechanism is under development which might + be added as an option in the future. + - name: Zihpm + presence: optional + version: "= 2.0" + - name: Sv48 + presence: optional + version: "= 1.11" \ No newline at end of file diff --git a/arch/profile/rva20s64.yaml b/arch/profile/rva20s64.yaml index eab1825c5e..48313c9222 100644 --- a/arch/profile/rva20s64.yaml +++ b/arch/profile/rva20s64.yaml @@ -7,7 +7,6 @@ rva20s64: processors. RVA20S64 is based on privileged architecture version 1.11. marketing_name: RVA20S64 - inherits: rva20u64 mode: S version: "1.0" contributors: diff --git a/arch/profile/rva20u64.yaml b/arch/profile/rva20u64.yaml index 195c29387e..6e0e07c03d 100644 --- a/arch/profile/rva20u64.yaml +++ b/arch/profile/rva20u64.yaml @@ -102,16 +102,15 @@ rva20u64: version: "= 2.0" note: | The number of counters is platform-specific. - - name: Q - presence: excluded + extra_notes: + - location: optional note: | The rationale to not make Q an optional extension is that quad-precision floating-point is unlikely to be implemented in hardware, and so we do not require or expect A-profile software to expend effort optimizing use of Q instructions in case they are present. - - name: Zifencei - presence: excluded + - location: optional note: | Zifencei is not classed as a supported option in the user-mode profile because it is not sufficient by itself to produce the desired @@ -123,7 +122,8 @@ rva20u64: instruction-cache coherence mechanisms can be used behind the OS abstraction. A separate extension is being developed for more general and efficient instruction cache coherence. - + - location: optional + note: | The execution environment must provide a means to synchronize writes to instruction memory with instruction fetches, the implementation of which likely relies on the Zifencei extension. diff --git a/arch/profile/rva22s64.yaml b/arch/profile/rva22s64.yaml index 03078f3f13..ee4c218efa 100644 --- a/arch/profile/rva22s64.yaml +++ b/arch/profile/rva22s64.yaml @@ -6,7 +6,6 @@ rva22s64: supervisor-mode execution environment in 64-bit applications processors. RVA22S64 is based on privileged architecture version 1.12. - inherits: rva22u64 version: "2.0" mode: S marketing_name: RVA22S64 diff --git a/arch/profile/rva22u64.yaml b/arch/profile/rva22u64.yaml index 7fb58cc60e..feab845599 100644 --- a/arch/profile/rva22u64.yaml +++ b/arch/profile/rva22u64.yaml @@ -99,34 +99,30 @@ rva22u64: - name: Zkn presence: optional version: "~> 1.0" - note: | - The scalar crypto extensions are expected to be superseded by - vector crypto standards in future profiles, and the scalar extensions - may be removed as supported options once vector crypto is present. - - The smaller component scalar crypto extensions (Zbc, Zbkb, Zbkc, - Zbkx, Zknd, Zkne, Zknh, Zksed, Zksh) are not provided as separate - options in the profile. Profile implementers should provide all of - the instructions in a given algorithm suite as part of the Zkn or Zks - supported options. - name: Zks presence: optional version: "~> 1.0" + extra_notes: + - location: optional note: | The scalar crypto extensions are expected to be superseded by vector crypto standards in future profiles, and the scalar extensions may be removed as supported options once vector crypto is present. - + - location: optional + note: | The smaller component scalar crypto extensions (Zbc, Zbkb, Zbkc, Zbkx, Zknd, Zkne, Zknh, Zksed, Zksh) are not provided as separate options in the profile. Profile implementers should provide all of the instructions in a given algorithm suite as part of the Zkn or Zks supported options. - - name: Zkr - presence: excluded + - location: optional note: | Access to the entropy source (Zkr) in a system is usually carefully controlled. While the design supports unprivileged access to the entropy source, this is unlikely to be commonly used in an application processor, and so Zkr was not added as a profile option. - This also means the roll-up Zk was not added as a profile option. \ No newline at end of file + This also means the roll-up Zk was not added as a profile option. + - location: optional + note: | + The Zfinx, Zdinx, Zhinx, Zhinxmin extensions are incompatible + with the profile mandates to support the F and D extensions. \ No newline at end of file diff --git a/arch/profile_family/MockProfileFamily.yaml b/arch/profile_family/MockProfileFamily.yaml new file mode 100644 index 0000000000..43c3eb58b7 --- /dev/null +++ b/arch/profile_family/MockProfileFamily.yaml @@ -0,0 +1,15 @@ + +MockProfileFamily: + marketing_name: Mock Profile Family + description: This is the Mock Profile Family description. + company: + name: RISC-V International + url: https://riscv.org + doc_license: + name: Creative Commons Attribution 4.0 International License + url: https://creativecommons.org/licenses/by/4.0/ + text_url: https://creativecommons.org/licenses/by/4.0/legalcode.txt + introduction: | + Here's the Mock Profile Family's introduction. + naming_scheme: | + Here's the Mock Profile Family's naming scheme. \ No newline at end of file diff --git a/arch/profile_family/rva.yaml b/arch/profile_family/rva.yaml index 4309ef1d6b..1ebf2fd411 100644 --- a/arch/profile_family/rva.yaml +++ b/arch/profile_family/rva.yaml @@ -1,6 +1,9 @@ rva: marketing_name: RVA + introduction: | + The RVA profiles target application processors for markets + requiring a high-degree of binary compatibility between compliant implementations. description: | The RVA profiles are intended to be used for 64-bit application processors running rich OS stacks. Only user-mode and @@ -18,6 +21,25 @@ rva: NOTE: Only XLEN=64 application processor profiles are currently defined. It would be possible to also define very similar XLEN=32 variants. + naming_scheme: | + A profile name is a string comprised of, in order: + + * Prefix *RV* for RISC-V. + * A specific profile family name string. Initially a single letter (*I*, *M*, or *A*), but later profiles may have longer family name strings. + * A numeric string giving the first complete calendar year for which the profile is ratified, represented as number of years after year 2000, i.e., *20* for profiles built on specifications ratified during 2019. The year string will be longer than two digits in the next century. + * A privilege mode (*U*, *S*, *M*). Hypervisor support is treated as an option. + * A base ISA XLEN specifier (*32*, *64*). + + The initial profiles based on specifications ratified in 2019 are: + + * RVI20U32 basic unprivileged instructions for RV32I + * RVI20U64 basic unprivileged instructions for RV64I + * RVA20U64, RVA20S64 64-bit application-processor profiles + + NOTE: Profile names are embeddable into RISC-V ISA naming strings. + This implies that there will be no standard ISA extension with a name + that matches the profile naming convention. This allows tools that + process the RISC-V ISA naming string to parse and/or process a combined string. company: name: RISC-V International url: https://riscv.org diff --git a/backends/crd_doc/tasks.rake b/backends/crd_doc/tasks.rake index 7a192bf680..661ca67628 100644 --- a/backends/crd_doc/tasks.rake +++ b/backends/crd_doc/tasks.rake @@ -32,6 +32,7 @@ Dir.glob("#{$root}/arch/crd/*.yaml") do |f| # switch to the generated CRD arch def arch_def = crd.to_arch_def crd = arch_def.crd(crd_name) + crd_family = crd.family version = File.basename(t.name, '.adoc').split('-')[1..].join('-') diff --git a/backends/crd_doc/templates/crd.adoc.erb b/backends/crd_doc/templates/crd.adoc.erb index e17221661a..c490ee96e0 100644 --- a/backends/crd_doc/templates/crd.adoc.erb +++ b/backends/crd_doc/templates/crd.adoc.erb @@ -20,17 +20,17 @@ = <%= crd.name %> Certification Requirements Document [Preface] -== <%= crd.family.name %> Family Revision History +== Revision History -Contains documentation changes that apply to all releases of the family. +History of documentation changes that eventually lead to releases. [cols="1,1,5"] |=== | Date | Revision | Changes -<% crd.family.revisions.each do |rev| -%> +<% crd.revision_history.each do |rev| -%> | <%= rev.date %> -| <%= rev.version %> +| <%= rev.revision %> a| <% rev.changes.each do |change| %> * <%= change %> <% end -%> @@ -60,11 +60,15 @@ CSR field types:: == Introduction -<%= crd.family.introduction %> +<%= crd_family.introduction %> -=== Naming Scheme +=== <%= crd_family.name %> Naming Scheme -<%= crd.family.naming_scheme %> +<%= crd_family.naming_scheme %> + +=== <%= crd_family.name %> Family Description + +<%= crd_family.description %> === <%= crd.name %> Description @@ -88,14 +92,15 @@ CSR field types:: |=== | M | S | U | VS | VU -| <% if crd.family.mandatory_priv_modes.include?('M') -%> MANDATORY <% else -%> OUT-OF-SCOPE <% end -%> -| <% if crd.family.mandatory_priv_modes.include?('S') -%> MANDATORY <% else -%> OUT-OF-SCOPE <% end -%> -| <% if crd.family.mandatory_priv_modes.include?('U') -%> MANDATORY <% else -%> OUT-OF-SCOPE <% end -%> -| <% if crd.family.mandatory_priv_modes.include?('VS') -%> MANDATORY <% else -%> OUT-OF-SCOPE <% end -%> -| <% if crd.family.mandatory_priv_modes.include?('VU') -%> MANDATORY <% else -%> OUT-OF-SCOPE <% end -%> +| <% if crd_family.mandatory_priv_modes.include?('M') -%> MANDATORY <% else -%> OUT-OF-SCOPE <% end -%> +| <% if crd_family.mandatory_priv_modes.include?('S') -%> MANDATORY <% else -%> OUT-OF-SCOPE <% end -%> +| <% if crd_family.mandatory_priv_modes.include?('U') -%> MANDATORY <% else -%> OUT-OF-SCOPE <% end -%> +| <% if crd_family.mandatory_priv_modes.include?('VS') -%> MANDATORY <% else -%> OUT-OF-SCOPE <% end -%> +| <% if crd_family.mandatory_priv_modes.include?('VU') -%> MANDATORY <% else -%> OUT-OF-SCOPE <% end -%> |=== +<<< == Extensions Any RISC-V extension not listed in this section is OUT-OF-SCOPE so the <%= crd.name %> @@ -125,6 +130,7 @@ None <% end # if table -%> <% end # do ext_reqs -%> +<<< == Implementation-dependencies RISC-V standards support many implementation-defined parameters. In many cases, there @@ -265,6 +271,7 @@ Requirement <%= req.name %> only apply when <%= req.when_pretty %>. <% end -%> <% end # unless requirement_groups.empty? -%> +<<< [appendix] == Extension Details <% crd.in_scope_ext_reqs.sort.each do |ext_req| -%> @@ -363,6 +370,7 @@ The following instructions are added by this extension: <% end # unless table -%> <% end # do ext_req -%> +<<< [appendix] == Instruction Details @@ -495,6 +503,7 @@ This instruction may result in the following synchronous exceptions: <% end -%> +<<< [appendix] == CSR Details diff --git a/backends/portfolio_doc/tasks.rb b/backends/portfolio_doc/tasks.rb new file mode 100644 index 0000000000..7a192bf680 --- /dev/null +++ b/backends/portfolio_doc/tasks.rb @@ -0,0 +1,122 @@ +# frozen_string_literal: true + +require "pathname" + +require "asciidoctor-pdf" +require "asciidoctor-diagram" + +require_relative "#{$lib}/idl/passes/gen_adoc" + +CRD_DOC_DIR = Pathname.new "#{$root}/backends/crd_doc" + +Dir.glob("#{$root}/arch/crd/*.yaml") do |f| + crd_name = File.basename(f, ".yaml") + crd_obj = YAML.load_file(f, permitted_classes: [Date]) + raise "Ill-formed CRD file #{f}: missing 'family' field" if crd_obj.dig(crd_name, 'family').nil? + + base = crd_obj[crd_name]["base"] + raise "Missing CRD base" if base.nil? + + file "#{$root}/gen/crd_doc/adoc/#{crd_name}.adoc" => [ + "#{$root}/arch/crd/#{crd_name}.yaml", + "#{$root}/arch/crd_family/#{crd_obj[crd_name]['family']}.yaml", + "#{CRD_DOC_DIR}/templates/crd.adoc.erb", + __FILE__, + "#{$root}/.stamps/arch-gen-_#{base}.stamp" + ] do |t| + # TODO: schema validation + arch_def = arch_def_for("_#{base}") + crd = arch_def.crd(crd_name) + raise "No CRD defined for #{crd_name}" if crd.nil? + + # switch to the generated CRD arch def + arch_def = crd.to_arch_def + crd = arch_def.crd(crd_name) + + version = File.basename(t.name, '.adoc').split('-')[1..].join('-') + + erb = ERB.new(File.read("#{CRD_DOC_DIR}/templates/crd.adoc.erb"), trim_mode: "-") + erb.filename = "#{CRD_DOC_DIR}/templates/crd.adoc.erb" + + FileUtils.mkdir_p File.dirname(t.name) + File.write t.name, AsciidocUtils.resolve_links(arch_def.find_replace_links(erb.result(binding))) + puts "Generated adoc source at #{t.name}" + end + + file "#{$root}/gen/crd_doc/pdf/#{crd_name}.pdf" => [ + "#{$root}/gen/crd_doc/adoc/#{crd_name}.adoc" + ] do |t| + adoc_file = "#{$root}/gen/crd_doc/adoc/#{crd_name}.adoc" + FileUtils.mkdir_p File.dirname(t.name) + sh [ + "asciidoctor-pdf", + "-w", + "-v", + "-a toc", + "-a compress", + "-a pdf-theme=#{$root}/ext/docs-resources/themes/riscv-pdf.yml", + "-a pdf-fontsdir=#{$root}/ext/docs-resources/fonts", + "-a imagesdir=#{$root}/ext/docs-resources/images", + "-r asciidoctor-diagram", + "-r #{$root}/backends/ext_pdf_doc/idl_lexer", + "-o #{t.name}", + adoc_file + ].join(" ") + end + + file "#{$root}/gen/crd_doc/html/#{crd_name}.html" => [ + "#{$root}/gen/crd_doc/adoc/#{crd_name}.adoc" + ] do |t| + adoc_file = "#{$root}/gen/crd_doc/adoc/#{crd_name}.adoc" + FileUtils.mkdir_p File.dirname(t.name) + sh [ + "asciidoctor", + "-w", + "-v", + "-a toc", + "-a imagesdir=#{$root}/ext/docs-resources/images", + "-b html5", + "-r asciidoctor-diagram", + "-r #{$root}/backends/ext_pdf_doc/idl_lexer", + "-o #{t.name}", + adoc_file + ].join(" ") + end + +end + +namespace :gen do + desc <<~DESC + Generate CRD documentation for a specific version as a PDF + + Required options: + crd_name - The key of the CRD Family under arch/crd + DESC + task :crd_pdf, [:crd_name] do |_t, args| + if args[:crd_name].nil? + warn "Missing required option: 'crd_family_name'" + exit 1 + end + + unless File.exist?("#{$root}/arch/crd/#{args[:crd_name]}.yaml") + warn "No CRD named '#{args[:crd_name]}' found in arch/crd" + exit 1 + end + + Rake::Task["#{$root}/gen/crd_doc/pdf/#{args[:crd_name]}.pdf"].invoke + end + + task :crd_html, [:crd_name] do |_t, args| + if args[:crd_name].nil? + warn "Missing required option: 'crd_family_name'" + exit 1 + end + + unless File.exist?("#{$root}/arch/crd/#{args[:crd_name]}.yaml") + warn "No CRD named '#{args[:crd_name]}' found in arch/crd" + exit 1 + end + + Rake::Task["#{$root}/gen/crd_doc/html/#{args[:crd_name]}.html"].invoke + end +end \ No newline at end of file diff --git a/backends/portfolio_doc/templates/family_intro.erb b/backends/portfolio_doc/templates/family_intro.erb new file mode 100644 index 0000000000..abe0caee0d --- /dev/null +++ b/backends/portfolio_doc/templates/family_intro.erb @@ -0,0 +1,11 @@ +== <%= portfolio_family.name %> Introduction + +<%= portfolio_family.introduction %> + +=== <%= portfolio_family.name %> Naming Scheme + +<%= portfolio_family.naming_scheme %> + +=== <%= portfolio_family.name %> Family Description + +<%= portfolio_family.description %> \ No newline at end of file diff --git a/backends/profile_doc/tasks.rake b/backends/profile_doc/tasks.rake index 687f961fcf..1b005d1860 100644 --- a/backends/profile_doc/tasks.rake +++ b/backends/profile_doc/tasks.rake @@ -7,7 +7,7 @@ rule %r{#{$root}/gen/profile_doc/adoc/.*\.adoc} => proc { |tname| "#{$root}/.stamps/arch-gen.stamp", __FILE__, "#{$root}/lib/arch_obj_models/profile.rb", - "#{$root}/backends/profile_doc/templates/profile_pdf.adoc.erb" + "#{$root}/backends/profile_doc/templates/profile.adoc.erb" ] + Dir.glob("#{$root}/arch/profile/**/*.yaml") } do |t| profile_family_name = Pathname.new(t.name).basename(".adoc").to_s @@ -15,7 +15,7 @@ rule %r{#{$root}/gen/profile_doc/adoc/.*\.adoc} => proc { |tname| profile_family = arch_def_for("_64").profile_family(profile_family_name) raise ArgumentError, "No profile family named '#{profile_family_name}'" if profile_family.nil? - template_path = Pathname.new "#{$root}/backends/profile_doc/templates/profile_pdf.adoc.erb" + template_path = Pathname.new "#{$root}/backends/profile_doc/templates/profile.adoc.erb" erb = ERB.new(template_path.read, trim_mode: "-") erb.filename = template_path.to_s @@ -81,7 +81,7 @@ end namespace :gen do desc "Create a specification PDF for +profile_family+" - task :profile_pdf, [:profile_family] => ["#{$root}/.stamps/arch-gen-_64.stamp"] do |_t, args| + task :profile, [:profile_family] => ["#{$root}/.stamps/arch-gen-_64.stamp"] do |_t, args| family_name = args[:profile_family] raise ArgumentError, "Missing required option +profile_family+" if family_name.nil? diff --git a/backends/profile_doc/templates/profile_pdf.adoc.erb b/backends/profile_doc/templates/profile.adoc.erb similarity index 98% rename from backends/profile_doc/templates/profile_pdf.adoc.erb rename to backends/profile_doc/templates/profile.adoc.erb index 4d03f12ba9..4e11d099aa 100644 --- a/backends/profile_doc/templates/profile_pdf.adoc.erb +++ b/backends/profile_doc/templates/profile.adoc.erb @@ -312,7 +312,20 @@ are by definition non-profile extensions. For example, mandatory or optional profile extensions for a new profile might be prototyped as non-profile extensions on an earlier profile. -== Profiles in the <%= profile_family.name %> family +// XXX - Need to create render() Ruby function. +// <%# render("#{$root}/backends/portfolio_doc/templates/family_intro.erb", portfolio_family: profile_family) %> + +== <%= profile_family.name %> Introduction + +<%= profile_family.introduction %> + +=== <%= profile_family.name %> Naming Scheme + +<%= profile_family.naming_scheme %> + +=== <%= profile_family.name %> Family Description + +<%= profile_family.description %> The following profiles are defined in this family: @@ -326,12 +339,7 @@ Ratification date:: <%= profile.ratification_date %> -- <%- end -%> -<<< -== Family description - -<%= profile_family.description %> - -=== Extension summary +=== Extensions in the <%= profile_family.name %> Family The <%= profile_family.marketing_name %> Profile Family references <%= profile_family.referenced_extensions.size %> extensions. @@ -406,6 +414,7 @@ Version<%= ext_req.version_requirement %> <%- end -%> <%- end -%> +<<< [appendix] == Extension Specifications @@ -498,6 +507,7 @@ This extension has the following implementation options: <%- end -%> <%- end -%> +<<< [appendix] == Instruction Specifications @@ -632,6 +642,7 @@ This instruction may result in the following synchronous exceptions: <%- end -%> +<<< [appendix] == CSR Specifications diff --git a/lib/arch_def.rb b/lib/arch_def.rb index 3374c9925a..0426afafcb 100644 --- a/lib/arch_def.rb +++ b/lib/arch_def.rb @@ -12,6 +12,7 @@ require_relative "idl/passes/reachable_functions_unevaluated" require_relative "idl/passes/reachable_exceptions" require_relative "arch_obj_models/manual" +require_relative "arch_obj_models/portfolio" require_relative "arch_obj_models/profile" require_relative "arch_obj_models/csr_field" require_relative "arch_obj_models/csr" @@ -632,7 +633,12 @@ def profiles @profiles = [] @arch_def["profiles"].each_value do |profile_data| - @profiles << Profile.new(profile_data, self) + raise ArgumentError, "Expecting profile_data to be a hash" unless profile_data.is_a?(Hash) + + profile = Profile.new(profile_data, self) + raise ArgumentError, "Profile constructor returned nil" if profile.nil? + + @profiles << profile end @profiles end @@ -672,8 +678,11 @@ def crd_famlies_hash @crd_families_hash end + # @return [CrdFamily] The CRD family named +name+ + # @return [nil] if the CRD family does not exist def crd_family(name) = crd_famlies_hash[name] + # @return [Crd] List of all defined CRDs def crds return @crds unless @crds.nil? @@ -694,6 +703,8 @@ def crds_hash @crds_hash end + # @return [Crd] The CRD named +name+ + # @return [nil] if the CRD does not exist def crd(name) = crds_hash[name] # @return [Array] All exception codes defined by RISC-V diff --git a/lib/arch_obj_models/crd.rb b/lib/arch_obj_models/crd.rb index a83abd998e..3fe1f961a1 100644 --- a/lib/arch_obj_models/crd.rb +++ b/lib/arch_obj_models/crd.rb @@ -1,33 +1,10 @@ # Classes for CRD (Certification Requirements Documents). # Each CRD is a member of a CRD family (e.g., Microcontroller). # -# Many classes inherit from the ArchDefObject class. This provides facilities for accessing the contents of a -# CRD family YAML or CRD YAML file via the "data" member (hash holding releated YAML file contents). -# -# Many classes have an "arch_def" member which is an ArchDef (not ArchDefObject) class. -# The "arch_def" member contains the "database" of RISC-V standards including extensions, instructions, CSRs, Profiles, and CRDs. -# The arch_def member has methods such as: -# extensions() Array of all extensions known to the database (even if not implemented). -# extension(name) Extension object for "name" and nil if none. -# parameters() Array of all parameters defined in the architecture -# param(name) ExtensionParameter object for "name" and nil if none. -# csrs() Array of all CSRs defined by RISC-V, whether or not they are implemented -# csr(name) Csr object for "name" and nil if none. -# instructions() Array of all instructions, whether or not they are implemented -# inst(name) Instruction object for "name" and nil if none. -# profile_families Array of all known profile families -# profile_family(name) ProfileFamily object for "name" and nil if none. -# profiles Array of all known profiles. -# profile(name) Profile object for "name" and nil if none. -# crd_families Array of all known CRD families -# crd_family(name) CrdFamily object for "name" and nil if none. -# crds Array of all known CRDs. -# crd(name) Crd object for "name" and nil if none. -# # A variable name with a "_crd" suffix indicates it is from the CRD family/member YAML file. # A variable name with a "_db" suffix indicates it is an object reference from the arch_def database. -require_relative "schema" +require_relative "portfolio" ################### # CrdFamily Class # @@ -35,62 +12,14 @@ # Holds information from CRD family YAML file. # The inherited "data" member is the database of extensions, instructions, CSRs, etc. -class CrdFamily < ArchDefObject - class Revision < ArchDefObject - def initialize(data) - super(data) - end - - def version - @data["version"] - end - - def date - @data["date"] - end - - def changes - @data["changes"] - end - end -end - -class CrdFamily < ArchDefObject - attr_reader :arch_def - +class CrdFamily < PortfolioFamily + # @param data [Hash] The data from YAML + # @param arch_def [ArchDef] Architecture spec def initialize(data, arch_def) - super(data) - @arch_def = arch_def + super(data, arch_def) end def mandatory_priv_modes = @data["mandatory_priv_modes"] - - def revisions - return @revisions unless @revisions.nil? - - @revisions = [] - @data["revision_history"].each do |rev| - @revisions << Revision.new(rev) - end - @revisions - end - - def introduction = @data["introduction"] - def naming_scheme = @data["naming_scheme"] - - def eql?(other) - other.is_a?(CrdFamily) && other.name == name - end - - def crds - return @crds unless @version.nil? - - @crds = [] - arch_def.crds.each do |crd| - @crds << crd if crd.famly == self - end - @crds - end end ############# @@ -99,24 +28,16 @@ def crds # Holds information about a CRD YAML file. # The inherited "data" member is the database of extensions, instructions, CSRs, etc. -class Crd < ArchDefObject - attr_reader :arch_def - +class Crd < Portfolio + # @param data [Hash] The data from YAML + # @param arch_def [ArchDef] Architecture spec def initialize(data, arch_def) - super(data) - @arch_def = arch_def + super(data, arch_def) end - def version = @data["version"] - - def family - return @family unless @family.nil? - - fam = @arch_def.crd_family(@data["family"]) - raise "No CRD family named '#{@data["family"]}'" if fam.nil? - - @family = fam - end + def unpriv_isa_manual_revision = @data["unpriv_isa_manual_revision"] + def priv_isa_manual_revision = @data["priv_isa_manual_revision"] + def debug_manual_revision = @data["debug_manual_revision"] def tsc_profile return nil if @data["tsc_profile"].nil? @@ -128,272 +49,34 @@ def tsc_profile profile end - def unpriv_isa_manual_revision = @data["unpriv_isa_manual_revision"] - def priv_isa_manual_revision = @data["priv_isa_manual_revision"] - def debug_manual_revision = @data["debug_manual_revision"] - def description = @data["description"] - - # @return [Extension] - # Returns named Extension object from database (nil if not found). - def extension_from_db(name) - @arch_def.extension(name) - end - - # @return [Array] List of all extensions listed as mandatory or optional in CRD. - def in_scope_extensions - in_scope_ext_reqs.map do |er| - obj = arch_def.extension(er.name) - - # @todo: change this to raise once all the profile extensions - # are defined - warn "Extension #{er.name} is not defined" if obj.nil? - - obj - end.reject(&:nil?) - end - - # @return [Array] - # Extensions with their CRD information. - # If desired_presence is provided, only returns extensions with that presence. - def in_scope_ext_reqs(desired_presence = nil) - in_scope_ext_reqs = [] - @data["extensions"]&.each do |ext_crd| - actual_presence = ext_crd["presence"] - raise "Missing extension presence for extension #{ext_crd["name"]}" if actual_presence.nil? - - if (actual_presence != "mandatory") && (actual_presence != "optional") - raise "Unknown extension presence of #{actual_presence} for extension #{ext_crd["name"]}" - end - - add = false - - if desired_presence.nil? - add = true - elsif desired_presence == actual_presence - add = true - end - - if add - in_scope_ext_reqs << - ExtensionRequirement.new(ext_crd["name"], ext_crd["version"], presence: actual_presence, - note: ext_crd["note"], req_id: "REQ-EXT-" + ext_crd["name"]) - end - end - in_scope_ext_reqs - end - - ################################### - # InScopeExtensionParameter Class # - ################################### - - # Holds extension parameter information from the CRD. - class InScopeExtensionParameter - attr_reader :param_db # ExtensionParameter object (from the architecture database) - attr_reader :note - - def initialize(param_db, schema_hash, note) - raise ArgumentError, "Expecting ExtensionParameter" unless param_db.is_a?(ExtensionParameter) - - if schema_hash.nil? - schema_hash = {} - else - raise ArgumentError, "Expecting schema_hash to be a hash" unless schema_hash.is_a?(Hash) - end - - @param_db = param_db - @schema_crd = Schema.new(schema_hash) - @note = note - end - - def single_value? - @schema_crd.single_value? - end - - def name - @param_db.name - end - - def idl_type - @param_db.type - end - - def value - raise "Parameter schema_crd for #{name} is not a single value" unless single_value? - - @schema_crd.value - end - - # @return [String] - # What parameter values are allowed by the CRD. - def allowed_values - if (@schema_crd.empty?) - # CRD doesn't add any constraints on parameter's value. - return "Any" - end - - # Create a Schema object just using information in the parameter database. - schema_obj = @param_db.schema - - # Merge in constraints imposed by the CRD on the parameter and then - # create string showing allowed values of parameter with CRD constraints added - schema_obj.merge(@schema_crd).to_pretty_s - end - - # sorts by name - def <=>(other) - raise ArgumentError, - "InScopeExtensionParameter are only comparable to other parameter constraints" unless other.is_a?(InScopeExtensionParameter) - @param_db.name <=> other.param_db.name - end - end # class InScopeExtensionParameter - - ############################################ - # Routines using InScopeExtensionParameter # - ############################################ - - # @return [Array] List of parameters specified by any extension in CRD. - # These are always IN SCOPE by definition (since they are listed in the CRD). - # Can have multiple array entries with the same parameter name since multiple extensions may define - # the same parameter. - def all_in_scope_ext_params - return @all_in_scope_ext_params unless @all_in_scope_ext_params.nil? - - @all_in_scope_ext_params = [] - - @data["extensions"].each do |ext_crd| - # Find Extension object from database - ext_db = @arch_def.extension(ext_crd["name"]) - raise "Cannot find extension named #{ext_crd["name"]}" if ext_db.nil? - - ext_crd["parameters"]&.each do |param_name, param_data| - param_db = ext_db.params.find { |p| p.name == param_name } - raise "There is no param '#{param_name}' in extension '#{ext_crd["name"]}" if param_db.nil? - - next unless ext_db.versions.any? do |ver| - Gem::Requirement.new(ext_crd["version"]).satisfied_by?(Gem::Version.new(ver["version"])) && - param_db.defined_in_extension_version?(ver["version"]) - end - - @all_in_scope_ext_params << - InScopeExtensionParameter.new(param_db, param_data["schema"], param_data["note"]) - end - end - @all_in_scope_ext_params - end - - # @return [Array] List of extension parameters from CRD for given extension. - # These are always IN SCOPE by definition (since they are listed in the CRD). - def in_scope_ext_params(ext_req) - raise ArgumentError, "Expecting ExtensionRequirement" unless ext_req.is_a?(ExtensionRequirement) - - ext_params = [] # Local variable, no caching - - # Get extension information from CRD YAML for passed in extension requirement. - ext_crd = @data["extensions"].find {|ext| ext["name"] == ext_req.name} - raise "Cannot find extension named #{ext_req.name}" if ext_crd.nil? - - # Find Extension object from database - ext_db = @arch_def.extension(ext_crd["name"]) - raise "Cannot find extension named #{ext_crd["name"]}" if ext_db.nil? - - # Loop through an extension's parameter constraints (hash) from the CRD. - # Note that "&" is the Ruby safe navigation operator (i.e., skip do loop if nil). - ext_crd["parameters"]&.each do |param_name, param_data| - # Find ExtensionParameter object from database - ext_param_db = ext_db.params.find { |p| p.name == param_name } - raise "There is no param '#{param_name}' in extension '#{ext_crd["name"]}" if ext_param_db.nil? - - next unless ext_db.versions.any? do |ver| - Gem::Requirement.new(ext_crd["version"]).satisfied_by?(Gem::Version.new(ver["version"])) && - ext_param_db.defined_in_extension_version?(ver["version"]) - end - - ext_params << - InScopeExtensionParameter.new(ext_param_db, param_data["schema"], param_data["note"]) - end - - ext_params - end - - # @return [Array] Parameters out of scope across all in scope extensions (those listed in the CRD). - def all_out_of_scope_params - return @all_out_of_scope_params unless @all_out_of_scope_params.nil? - - @all_out_of_scope_params = [] - in_scope_ext_reqs.each do |ext_req| - ext_db = @arch_def.extension(ext_req.name) - ext_db.params.each do |param_db| - next if all_in_scope_ext_params.any? { |c| c.param_db.name == param_db.name } - - next unless ext_db.versions.any? do |ver| - Gem::Requirement.new(ext_req.version_requirement).satisfied_by?(Gem::Version.new(ver["version"])) && - param_db.defined_in_extension_version?(ver["version"]) - end - - @all_out_of_scope_params << param_db - end - end - @all_out_of_scope_params - end - - # @return [Array] Parameters that are out of scope for named extension. - def out_of_scope_params(ext_name) - all_out_of_scope_params.select{|param_db| param_db.exts.any? {|ext| ext.name == ext_name} } - end - - # @return [Array] - # All the in-scope extensions (those in the CRD) that define this parameter in the database - # and the parameter is in-scope (listed in that extension's list of parameters in the CRD). - def all_in_scope_exts_with_param(param_db) - raise ArgumentError, "Expecting ExtensionParameter" unless param_db.is_a?(ExtensionParameter) - - exts = [] - - # Interate through all the extensions in the architecture database that define this parameter. - param_db.exts.each do |ext_in_db| - found = false - - in_scope_extensions.each do |in_scope_ext| - if ext_in_db.name == in_scope_ext.name - found = true - next - end - end - - if found - # Only add extensions that exist in this CRD. - exts << ext_in_db - end - end + # @return [CrdFamily] The CRD family specified by this profile. + def family + family = @arch_def.crd_family(@data["family"]) + raise "No CRD family named '#{@data["family"]}'" if family.nil? - # Return intersection of extension names - exts + family end - # @return [Array] - # All the in-scope extensions (those in the CRD) that define this parameter in the database - # but the parameter is out-of-scope (not listed in that extension's list of parameters in the CRD). - def all_in_scope_exts_without_param(param_db) - raise ArgumentError, "Expecting ExtensionParameter" unless param_db.is_a?(ExtensionParameter) - - exts = [] # Local variable, no caching - - # Interate through all the extensions in the architecture database that define this parameter. - param_db.exts.each do |ext_in_db| - found = false + # @return [ArchDef] A partially-configued architecture definition corresponding to this CRD + # XXX - Why doesn't profile have this? + def to_arch_def + return @generated_arch_def unless @generated_arch_def.nil? - in_scope_extensions.each do |in_scope_ext| - if ext_in_db.name == in_scope_ext.name - found = true - next - end - end + arch_def_data = arch_def.unconfigured_data - if found - # Only add extensions that are in-scope (i.e., exist in this CRD). - exts << ext_in_db - end + arch_def_data["mandatory_extensions"] = in_scope_ext_reqs("mandatory").map do |ext_req| + { + "name" => ext_req.name, + "version" => ext_req.version_requirement.requirements.map { |r| "#{r[0]} #{r[1]}" } + } end + arch_def_data["params"] = all_in_scope_ext_params.select(&:single_value?).map { |p| [p.name, p.value] }.to_h - # Return intersection of extension names - exts + file = Tempfile.new("archdef") + file.write(YAML.safe_dump(arch_def_data, permitted_classes: [Date])) + file.flush + file.close + @generated_arch_def = ArchDef.new(name, Pathname.new(file.path)) end ##################### @@ -479,25 +162,5 @@ def requirement_groups @requirement_groups end - # @return [ArchDef] A partially-configued architecture definition corresponding to this CRD - def to_arch_def - return @generated_arch_def unless @generated_arch_def.nil? - - arch_def_data = arch_def.unconfigured_data - - arch_def_data["mandatory_extensions"] = in_scope_ext_reqs("mandatory").map do |ext_req| - { - "name" => ext_req.name, - "version" => ext_req.version_requirement.requirements.map { |r| "#{r[0]} #{r[1]}" } - } - end - arch_def_data["params"] = all_in_scope_ext_params.select(&:single_value?).map { |p| [p.name, p.value] }.to_h - - file = Tempfile.new("archdef") - file.write(YAML.safe_dump(arch_def_data, permitted_classes: [Date])) - file.flush - file.close - @generated_arch_def = ArchDef.new(name, Pathname.new(file.path)) - end end \ No newline at end of file diff --git a/lib/arch_obj_models/portfolio.rb b/lib/arch_obj_models/portfolio.rb new file mode 100644 index 0000000000..082f8fd51a --- /dev/null +++ b/lib/arch_obj_models/portfolio.rb @@ -0,0 +1,460 @@ +# Classes for Porfolios which form a common base class for Profiles and CRDs. +# A "Portfolio" is a named & versioned grouping of extensions (each with a name and version). +# Each Portfolio is a member of a Portfolio Family (e.g., Microcontroller). +# +# Many classes inherit from the ArchDefObject class. This provides facilities for accessing the contents of a +# Portfolio Family YAML or Portfolio YAML file via the "data" member (hash holding releated YAML file contents). +# +# Many classes have an "arch_def" member which is an ArchDef (not ArchDefObject) class. +# The "arch_def" member contains the "database" of RISC-V standards including extensions, instructions, CSRs, Profiles, and CRDs. +# The arch_def member has methods such as: +# extensions() Array of all extensions known to the database (even if not implemented). +# extension(name) Extension object for "name" and nil if none. +# parameters() Array of all parameters defined in the architecture +# param(name) ExtensionParameter object for "name" and nil if none. +# csrs() Array of all CSRs defined by RISC-V, whether or not they are implemented +# csr(name) Csr object for "name" and nil if none. +# instructions() Array of all instructions, whether or not they are implemented +# inst(name) Instruction object for "name" and nil if none. +# profile_families Array of all known profile families +# profile_family(name) ProfileFamily object for "name" and nil if none. +# profiles Array of all known profiles. +# profile(name) Profile object for "name" and nil if none. +# crd_families Array of all known CRD families +# crd_family(name) CrdFamily object for "name" and nil if none. +# crds Array of all known CRDs. +# crd(name) Crd object for "name" and nil if none. +# +# A variable name with a "_portfolio" suffix indicates it is from the porfolio YAML file. +# A variable name with a "_db" suffix indicates it is an object reference from the arch_def database. + +require_relative "obj" +require_relative "schema" + +######################## +# PortfolioFamily Class # +######################## + +# Holds information from Portfolio family YAML file (CRD family or Profile family). +# The inherited "data" member is the database of extensions, instructions, CSRs, etc. +class PortfolioFamily < ArchDefObject + # @return [ArchDef] The defining ArchDef + attr_reader :arch_def + + # @param data [Hash] The data from YAML + # @param arch_def [ArchDef] Architecture spec + def initialize(data, arch_def) + super(data) + @arch_def = arch_def + end + + def introduction = @data["introduction"] + def naming_scheme = @data["naming_scheme"] + def description = @data["description"] + + # Returns true if other is the same class (not a derived class) and has the same name. + def eql?(other) + other.instance_of?(self.class) && other.name == name + end +end + +################### +# Portfolio Class # +################### + +# Holds information about a Portfolio YAML file (CRD or Profile). +# The inherited "data" member is the database of extensions, instructions, CSRs, etc. +class Portfolio < ArchDefObject + # @return [ArchDef] The defining ArchDef + attr_reader :arch_def + + # @param data [Hash] The data from YAML + # @param arch_def [ArchDef] Architecture spec + def initialize(data, arch_def) + super(data) + @arch_def = arch_def + end + + def description = @data["description"] + + # @return [Gem::Version] Semantic version of the Portfolio + def version = Gem::Version.new(@data["version"]) + + # @return [Extension] - Returns named Extension object from database (nil if not found). + def extension_from_db(ext_name) + @arch_def.extension(ext_name) + end + + # @return [Extension] - Returns named Extension object from portfolio (error if not found). + def extension_from_portfolio(ext_name) + # Get extension information from YAML for passed in extension name. + ext_portfolio = @data["extensions"].find {|ext| ext["name"] == ext_name} + raise "Cannot find extension named #{ext_name}" if ext_portfolio.nil? + + ext_portfolio + end + + # @return [String] Given an extension +ext_name+, return the presence. + # If the extension name isn't found in the portfolio, return "-". + def extension_presence(ext_name) + # Get extension information from YAML for passed in extension name. + ext_portfolio = @data["extensions"].find {|ext| ext["name"] == ext_name} + + ext_portfolio.nil? ? "-" : ext_portfolio["presence"] + end + + # @return [String] The note associated with extension +ext_name+ + # @return [nil] if there is no note for +ext_name+ + def extension_note(ext_name) + ext = extension_from_portfolio(ext_name) + + return ext["note"] unless ext.nil? + end + + # @return [Array] - # Extensions with their portfolio information. + # If desired_presence is provided, only returns extensions with that presence. + def in_scope_ext_reqs(desired_presence = nil) + in_scope_ext_reqs = [] + @data["extensions"]&.each do |ext_portfolio| + actual_presence = ext_portfolio["presence"] + raise "Missing extension presence for extension #{ext_portfolio["name"]}" if actual_presence.nil? + + if (actual_presence != "mandatory") && (actual_presence != "optional") + raise "Unknown extension presence of #{actual_presence} for extension #{ext_portfolio["name"]}" + end + + add = false + + if desired_presence.nil? + add = true + elsif desired_presence == actual_presence + add = true + end + + if add + in_scope_ext_reqs << + ExtensionRequirement.new(ext_portfolio["name"], ext_portfolio["version"], presence: actual_presence, + note: ext_portfolio["note"], req_id: "REQ-EXT-" + ext_portfolio["name"]) + end + end + in_scope_ext_reqs + end + + # @return [Array] List of all extensions listed in portfolio. + def in_scope_extensions + return @in_scope_extensions unless @in_scope_extensions.nil? + + @in_scope_extensions = in_scope_ext_reqs.map do |er| + obj = arch_def.extension(er.name) + + # @todo: change this to raise once all the profile extensions + # are defined + warn "Extension #{er.name} is not defined" if obj.nil? + + obj + end.reject(&:nil?) + + @in_scope_extensions + end + + # @return [Array] List of mandatory extensions listed in the portfolio. + def mandatory_extension_requirements + return @mandatory_ext_reqs unless @mandatory_ext_reqs.nil? + + @mandatory_ext_reqs = in_scope_ext_reqs("mandatory") + end + + # @return [Array] List of mandatory extensions listed in the portfolio. + def mandatory_extensions + mandatory_extension_requirements.map do |e| + obj = arch_def.extension(e.name) + + # @todo: change this to raise once all the profile extensions + # are defined + warn "Extension #{e.name} is not defined" if obj.nil? + + obj + end.reject(&:nil?) + end + + # @return [Boolean] whether or not +ext_name+ is mandatory in the portfolio. + def mandatory?(ext_name) + mandatory_extension_requirements.any? { |ext| ext.name == ext_name } + end + + # @return [Array] List of optional extensions listed in the portfolio. + def optional_extension_requirements + return @optional_ext_reqs unless @optional_ext_reqs.nil? + + @optional_ext_reqs = in_scope_ext_reqs("optional") + end + + # @return [Array] List of optional extensions listed in the portfolio. + def optional_extensions + optional_extension_requirements.map do |e| + obj = arch_def.extension(e.name) + + # @todo: change this to raise once all the profile extensions + # are defined + warn "Extension #{e.name} is not defined" if obj.nil? + + obj + end.reject(&:nil?) + end + + # @return [Boolean] whether or not +ext_name+ is optional in the prfoile + def optional?(ext_name) + optional_extension_requirements.any? { |ext| ext.name == ext_name } + end + + ################################### + # InScopeExtensionParameter Class # + ################################### + + # Holds extension parameter information from the portfolio. + class InScopeExtensionParameter + attr_reader :param_db # ExtensionParameter object (from the architecture database) + attr_reader :note + + def initialize(param_db, schema_hash, note) + raise ArgumentError, "Expecting ExtensionParameter" unless param_db.is_a?(ExtensionParameter) + + if schema_hash.nil? + schema_hash = {} + else + raise ArgumentError, "Expecting schema_hash to be a hash" unless schema_hash.is_a?(Hash) + end + + @param_db = param_db + @schema_portfolio = Schema.new(schema_hash) + @note = note + end + + def single_value? + @schema_portfolio.single_value? + end + + def name + @param_db.name + end + + def idl_type + @param_db.type + end + + def value + raise "Parameter schema_portfolio for #{name} is not a single value" unless single_value? + + @schema_portfolio.value + end + + # @return [String] - # What parameter values are allowed by the portfolio. + def allowed_values + if (@schema_portfolio.empty?) + # Portfolio doesn't add any constraints on parameter's value. + return "Any" + end + + # Create a Schema object just using information in the parameter database. + schema_obj = @param_db.schema + + # Merge in constraints imposed by the portfolio on the parameter and then + # create string showing allowed values of parameter with portfolio constraints added. + schema_obj.merge(@schema_portfolio).to_pretty_s + end + + # sorts by name + def <=>(other) + raise ArgumentError, + "InScopeExtensionParameter are only comparable to other parameter constraints" unless other.is_a?(InScopeExtensionParameter) + @param_db.name <=> other.param_db.name + end + end # class InScopeExtensionParameter + + ############################################ + # Routines using InScopeExtensionParameter # + ############################################ + + # @return [Array] List of parameters specified by any extension in portfolio. + # These are always IN-SCOPE by definition (since they are listed in the portfolio). + # Can have multiple array entries with the same parameter name since multiple extensions may define + # the same parameter. + def all_in_scope_ext_params + return @all_in_scope_ext_params unless @all_in_scope_ext_params.nil? + + @all_in_scope_ext_params = [] + + @data["extensions"].each do |ext_portfolio| + # Find Extension object from database + ext_db = @arch_def.extension(ext_portfolio["name"]) + raise "Cannot find extension named #{ext_portfolio["name"]}" if ext_db.nil? + + ext_portfolio["parameters"]&.each do |param_name, param_data| + param_db = ext_db.params.find { |p| p.name == param_name } + raise "There is no param '#{param_name}' in extension '#{ext_portfolio["name"]}" if param_db.nil? + + next unless ext_db.versions.any? do |ver_hash| + Gem::Requirement.new(ext_portfolio["version"]).satisfied_by?(Gem::Version.new(ver_hash["version"])) && + param_db.defined_in_extension_version?(ver_hash["version"]) + end + + @all_in_scope_ext_params << + InScopeExtensionParameter.new(param_db, param_data["schema"], param_data["note"]) + end + end + @all_in_scope_ext_params + end + + # @return [Array] List of extension parameters from portfolio for given extension. + # These are always IN SCOPE by definition (since they are listed in the portfolio). + def in_scope_ext_params(ext_req) + raise ArgumentError, "Expecting ExtensionRequirement" unless ext_req.is_a?(ExtensionRequirement) + + ext_params = [] # Local variable, no caching + + # Get extension information from portfolio YAML for passed in extension requirement. + ext_portfolio = @data["extensions"].find {|ext| ext["name"] == ext_req.name} + raise "Cannot find extension named #{ext_req.name}" if ext_portfolio.nil? + + # Find Extension object from database + ext_db = @arch_def.extension(ext_portfolio["name"]) + raise "Cannot find extension named #{ext_portfolio["name"]}" if ext_db.nil? + + # Loop through an extension's parameter constraints (hash) from the portfolio. + # Note that "&" is the Ruby safe navigation operator (i.e., skip do loop if nil). + ext_portfolio["parameters"]&.each do |param_name, param_data| + # Find ExtensionParameter object from database + ext_param_db = ext_db.params.find { |p| p.name == param_name } + raise "There is no param '#{param_name}' in extension '#{ext_portfolio["name"]}" if ext_param_db.nil? + + next unless ext_db.versions.any? do |ver_hash| + Gem::Requirement.new(ext_portfolio["version"]).satisfied_by?(Gem::Version.new(ver_hash["version"])) && + ext_param_db.defined_in_extension_version?(ver_hash["version"]) + end + + ext_params << + InScopeExtensionParameter.new(ext_param_db, param_data["schema"], param_data["note"]) + end + + ext_params + end + + # @return [Array] Parameters out of scope across all in scope extensions (those listed in the portfolio). + def all_out_of_scope_params + return @all_out_of_scope_params unless @all_out_of_scope_params.nil? + + @all_out_of_scope_params = [] + in_scope_ext_reqs.each do |ext_req| + ext_db = @arch_def.extension(ext_req.name) + ext_db.params.each do |param_db| + next if all_in_scope_ext_params.any? { |c| c.param_db.name == param_db.name } + + next unless ext_db.versions.any? do |ver_hash| + Gem::Requirement.new(ext_req.version_requirement).satisfied_by?(Gem::Version.new(ver_hash["version"])) && + param_db.defined_in_extension_version?(ver_hash["version"]) + end + + @all_out_of_scope_params << param_db + end + end + @all_out_of_scope_params + end + + # @return [Array] Parameters that are out of scope for named extension. + def out_of_scope_params(ext_name) + all_out_of_scope_params.select{|param_db| param_db.exts.any? {|ext| ext.name == ext_name} } + end + + # @return [Array] + # All the in-scope extensions (those in the portfolio) that define this parameter in the database + # and the parameter is in-scope (listed in that extension's list of parameters in the portfolio). + def all_in_scope_exts_with_param(param_db) + raise ArgumentError, "Expecting ExtensionParameter" unless param_db.is_a?(ExtensionParameter) + + exts = [] + + # Interate through all the extensions in the architecture database that define this parameter. + param_db.exts.each do |ext_in_db| + found = false + + in_scope_extensions.each do |in_scope_ext| + if ext_in_db.name == in_scope_ext.name + found = true + next + end + end + + if found + # Only add extensions that exist in this portfolio. + exts << ext_in_db + end + end + + # Return intersection of extension names + exts + end + + # @return [Array] + # All the in-scope extensions (those in the portfolio) that define this parameter in the database + # but the parameter is out-of-scope (not listed in that extension's list of parameters in the portfolio). + def all_in_scope_exts_without_param(param_db) + raise ArgumentError, "Expecting ExtensionParameter" unless param_db.is_a?(ExtensionParameter) + + exts = [] # Local variable, no caching + + # Interate through all the extensions in the architecture database that define this parameter. + param_db.exts.each do |ext_in_db| + found = false + + in_scope_extensions.each do |in_scope_ext| + if ext_in_db.name == in_scope_ext.name + found = true + next + end + end + + if found + # Only add extensions that are in-scope (i.e., exist in this portfolio). + exts << ext_in_db + end + end + + # Return intersection of extension names + exts + end + + ############################ + # RevisionHistory Subclass # + ############################ + + # Tracks history of portfolio document. This is separate from its version since + # a document may be revised several times before a new version is released. + + class RevisionHistory < ArchDefObject + def initialize(data) + super(data) + end + + def revision + @data["revision"] + end + + def date + @data["date"] + end + + def changes + @data["changes"] + end + end + + def revision_history + return @revision_history unless @revision_history.nil? + + @revision_history = [] + @data["revision_history"].each do |rev| + @revision_history << RevisionHistory.new(rev) + end + @revision_history + end +end \ No newline at end of file diff --git a/lib/arch_obj_models/profile.rb b/lib/arch_obj_models/profile.rb index 0d88e6b76a..c401b5d9fc 100644 --- a/lib/arch_obj_models/profile.rb +++ b/lib/arch_obj_models/profile.rb @@ -1,234 +1,106 @@ # frozen_string_literal: true -require_relative "obj" - -# representation of a specific profile for a Family, Mode, and Base -class Profile < ArchDefObject - # @return [ArchDef] The defining ArchDef - attr_reader :arch_def +require_relative "portfolio" +# A profile family is a set of profiles that share a common goal or lineage +# +# For example, the RVA family is a set of profiles for application processors +class ProfileFamily < PortfolioFamily + # @param data [Hash] The data from YAML + # @param arch_def [ArchDef] Architecture spec def initialize(data, arch_def) - super(data) - @arch_def = arch_def + super(data, arch_def) end - def family = arch_def.profile_family(@data["family"]) - - # @return [Profile] Profile that this profile inherits from - # @return [nil] if this profile has no parent - def inherits_from = arch_def.profile(@data["inherits"]) - - # @return ["M", "S", "U", "VS", "VU"] Privilege mode for the profile - def mode - if @data["mode"].nil? - raise "No mode specified and no inheritance for profile '#{name}'" if inherits_from.nil? - inherits_from.mode - else - @data["mode"] - end - end - - # @return [32, 64] The base XLEN for the profile - def base - if @data["base"].nil? - raise "No base specified and no inheritance for profile '#{name}'" if inherits_from.nil? - - inherits_from.base - else - @data["base"] - end - end - - # @return [Gem::Version] Semantic version of the Profile within the ProfileLineage - def version = Gem::Version.new(@data["version"]) - - # @return [String] The marketing name of the Profile + # @return [String] Name of the family def marketing_name = @data["marketing_name"] - # @return [String] State of the profile spec - def state = @data["state"] - - # @return [Date] Ratification date - # @return [nil] if the profile is not ratified - def ratification_date - return nil if @data["ratification_date"].nil? - - Date.parse(@data["ratification_date"]) - end - - # @return [Array] Contributors to the profile spec - def contributors - @data["contributors"].map { |data| Person.new(data) } - end + # @return [Array] Privilege modes that this family defines profiles for + def modes = @data["modes"] - # @return [String] Given an extension +ext_name+, return the presence - def extension_presence(ext_name) - if mandatory?(ext_name) - req = mandatory_extension_requirements.find do |req| - req.name == ext_name - end.version_requirement - "Mandatory, #{req}" - elsif optional?(ext_name) - req = optional_extension_requirements.find do |req| - req.name == ext_name - end.version_requirement - "Optional, #{req}" - else - "-" - end - end + # @return [Company] Company that created the profile + def company = Company.new(@data["company"]) - # @return [String] The note associated with extension +ext_name+ - # @return [nil] if there is no note for +ext_name+ - def extension_note(ext_name) - ext = @data["extensions"].find { |e| e["name"] == ext_name } - return ext["note"] unless ext.nil? + # @return [License] Documentation license + def doc_license + License.new(@data["doc_license"]) end - # @return [Array] List of mandatory extensions for the profile - def mandatory_extension_requirements - return @mandatory_ext_reqs unless @mandatory_ext_reqs.nil? - - @mandatory_ext_reqs = [] - @mandatory_ext_reqs += inherits_from.mandatory_extension_requirements unless inherits_from.nil? - - # we need to remove anything that was changed from inheritance - unless @data["extensions"].nil? - @mandatory_ext_reqs.delete_if { |ext_req| - @data["extensions"]&.any? { |opt| opt["name"] == ext_req.name } - } - - @data["extensions"]&.each do |ext_req| - if ext_req["presence"] == "mandatory" - @mandatory_ext_reqs << ExtensionRequirement.new(ext_req["name"], ext_req["version"], presence: "mandatory") - end - end + # @return [Date] The most recent ratification date of any profile in the family + # @return [nil] if there are no ratified profiles in the family + def ratification_date + date = nil + profiles.each do |profile| + date = profile.ratification_date if !profile.ratification_date.nil? && profile.ratification_date < date end - - @mandatory_ext_reqs + date end - # @return [Array] List of mandatory extensions - def mandatory_extensions - mandatory_extension_requirements.map do |e| - obj = arch_def.extension(e.name) - - # @todo: change this to raise once all the profile extensions - # are defined - warn "Extension #{e.name} is not defined" if obj.nil? + # @return [Array] Defined profiles in this family + def profiles + return @profiles unless @profiles.nil? - obj - end.reject(&:nil?) - end + @profiles = arch_def.profiles.select { |profile| profile.data["family"] == name } - # @return [Boolean] whether or not +ext_name+ is mandatory in the prfoile - def mandatory?(ext_name) - mandatory_extension_requirements.any? { |ext| ext.name == ext_name } + @profiles end - # @return [Array] List of optional extensions for the profile - def optional_extension_requirements - return @optional_ext_reqs unless @optional_ext_reqs.nil? - - @optional_ext_reqs = [] - @optional_ext_reqs += inherits_from.optional_extension_requirements unless inherits_from.nil? - - # we need to remove anything that was changed from inheritance - unless @data["extensions"].nil? - @optional_ext_reqs.delete_if { |ext_req| - @data["extensions"]&.any? { |opt| opt["name"] == ext_req.name } - } + # @return [Array] List of all extensions referenced by the family + def referenced_extensions + return @referenced_extensions unless @referenced_extensions.nil? - @data["extensions"]&.each do |ext_ver| - if ext_ver["presence"] == "optional" - @optional_ext_reqs << ExtensionRequirement.new(ext_ver["name"], ext_ver["version"], presence: "optional") - end - end + @referenced_extensions = [] + profiles.each do |profile| + @referenced_extensions += profile.in_scope_extensions end - @optional_ext_reqs - end - - # @return [Array] List of optional extensions - def optional_extensions - optional_extension_requirements.map do |e| - obj = arch_def.extension(e.name) - - # @todo: change this to raise once all the profile extensions - # are defined - warn "Extension #{e.name} is not defined" if obj.nil? - - obj - end.reject(&:nil?) - end + @referenced_extensions.uniq!(&:name) - # @return [Boolean] whether or not +ext_name+ is optional in the prfoile - def optional?(ext_name) - optional_extension_requirements.any? { |ext| ext.name == ext_name } + @referenced_extensions end end -# A profile family is a set of profiles that share a common goal or lineage -# -# For example, the RVA family is a set of profiles for application processors -class ProfileFamily < ArchDefObject - # @return [ArchDef] The defining ArchDef - attr_reader :arch_def - - # @return [String] Name of the family - def name = @data["name"] - - # @return [String] Name of the family - def marketing_name = @data["marketing_name"] - - # @param arch_def [ArchDef] Architecture spec - # @param profile_family_data [Hash] The data from YAML - def initialize(profile_family_data, arch_def) - super(profile_family_data) - @arch_def = arch_def +# representation of a specific profile for a Family, Mode, and Base +class Profile < Portfolio + def initialize(data, arch_def) + super(data, arch_def) end - def description = @data["description"] + # @return [String] The marketing name of the Profile + def marketing_name = @data["marketing_name"] - # @return [Array] Privilege modes that this family defines profiles for - def modes = @data["modes"] + # @return [String] State of the profile spec + def state = @data["state"] - # @return [Array] Defined profiles in this family - def profiles - return @profiles unless @profiles.nil? + # @return [ProfileFamily] The profile family specified by this profile. + def family + family = @arch_def.profile_family(@data["family"]) + raise "No profile family named '#{@data["family"]}'" if family.nil? - @profiles = arch_def.profiles.select { |profile| profile.data["family"] == name } + family end - # @return [Date] The most recent ratification date of any profile in the family - # @return [nil] if there are no ratified profiles in the family - def ratification_date - date = nil - profiles.each do |profile| - date = profile.ratification_date if !profile.ratification_date.nil? && profile.ratification_date < date - end - date + # @return ["M", "S", "U", "VS", "VU"] Privilege mode for the profile + def mode + @data["mode"] end - # @return [Array] List of all extensions referenced by the family - def referenced_extensions - return @referenced_extensions unless @referenced_extensions.nil? + # @return [32, 64] The base XLEN for the profile + def base + @data["base"] + end - @referenced_extensions = [] - profiles.each do |profile| - @referenced_extensions += profile.mandatory_extensions - @referenced_extensions += profile.optional_extensions - end + # @return [Date] Ratification date + # @return [nil] if the profile is not ratified + def ratification_date + return nil if @data["ratification_date"].nil? - @referenced_extensions.uniq!(&:name) + Date.parse(@data["ratification_date"]) end - # @return [Company] Company that created the profile - def company = Company.new(@data["company"]) - - # @return [License] Documentation license - def doc_license - License.new(@data["doc_license"]) + # @return [Array] Contributors to the profile spec + def contributors + @data["contributors"].map { |data| Person.new(data) } end end