diff --git a/.github/workflows/pages.yml b/.github/workflows/pages.yml index 4060bfaf1..0db28f0de 100644 --- a/.github/workflows/pages.yml +++ b/.github/workflows/pages.yml @@ -60,33 +60,33 @@ jobs: - name: Copy cfg html run: cp -R gen/cfg_html_doc/generic_rv64/html _site/example_cfg - name: Create RVA20 Profile Release PDF Spec - run: ./do gen:profile[RVA20] + run: ./do gen:profile_release_pdf[RVA20] - name: Copy RVA20 Profile Release PDF - run: cp gen/profile_doc/pdf/RVA20.pdf _site/pdfs/RVA20.pdf + run: cp gen/profile/pdf/RVA20ProfileRelease.pdf _site/pdfs - name: Create RVA22 Profile Release PDF Spec - run: ./do gen:profile[RVA22] + run: ./do gen:profile_release_pdf[RVA22] - name: Copy RVA22 Profile Release PDF - run: cp gen/profile_doc/pdf/RVA22.pdf _site/pdfs/RVA22.pdf + run: cp gen/profile/pdf/RVA22ProfileRelease.pdf _site/pdfs + - name: Create RVA23 Profile Release PDF Spec + run: ./do gen:profile_release_pdf[RVA23] + - name: Copy RVA23 Profile Release PDF + run: cp gen/profile/pdf/RVA23ProfileRelease.pdf _site/pdfs + - name: Create RVB23 Profile Release PDF Spec + run: ./do gen:profile_release_pdf[RVB23] + - name: Copy RVB23 Profile Release PDF + run: cp gen/profile/pdf/RVB23ProfileRelease.pdf _site/pdfs - name: Create RVI20 Profile Release PDF Spec - run: ./do gen:profile[RVI20] + run: ./do gen:profile_release_pdf[RVI20] - name: Copy RVI20 Profile Release PDF - run: cp gen/profile_doc/pdf/RVA20.pdf _site/pdfs/RVI20.pdf - - name: Create MC100-32 PDF Spec - run: ./do gen:cert_model_pdf[MC100-32] - - name: Copy MC100-32 PDF - run: cp gen/certificate_doc/pdf/MC100-32.pdf _site/pdfs/MC100-32.pdf - - name: Create MC100-32 HTML Spec - run: ./do gen:cert_model_html[MC100-32] - - name: Copy MC100-32 HTML - run: cp gen/certificate_doc/html/MC100-32.html _site/htmls/MC100-32.html - - name: Create MC100-64 PDF Spec - run: ./do gen:cert_model_pdf[MC100-64] - - name: Copy MC100-64 PDF - run: cp gen/certificate_doc/pdf/MC100-64.pdf _site/pdfs/MC100-64.pdf - - name: Create MC100-64 HTML Spec - run: ./do gen:cert_model_html[MC100-64] - - name: Copy MC100-64 HTML - run: cp gen/certificate_doc/html/MC100-64.html _site/htmls/MC100-64.html + run: cp gen/profile/pdf/RVA20ProfileRelease.pdf _site/pdfs + - name: Create MC100-32-CRD PDF Spec + run: ./do gen:proc_crd_pdf[MC100-32] + - name: Copy MC100-32-CRD PDF + run: cp gen/proc_crd/pdf/MC100-32-CRD.pdf _site/pdfs + - name: Create MC100-64-CRD PDF Spec + run: ./do gen:proc_crd_pdf[MC100-64] + - name: Copy MC100-64-CRD PDF + run: cp gen/proc_crd/pdf/MC100-64-CRD.pdf _site/pdfs - name: Copy manual html run: cp -R gen/manual/isa/top/all/html _site/manual - name: Setup Pages diff --git a/.github/workflows/regress.yml b/.github/workflows/regress.yml index 83f06dc2f..cb6e8e17f 100644 --- a/.github/workflows/regress.yml +++ b/.github/workflows/regress.yml @@ -156,7 +156,7 @@ jobs: name: Build container run: ./bin/build_container - name: Generate extension PDF - run: ./do gen:cert_model_pdf[MockCertificateModel] + run: ./do gen:proc_crd_pdf[MockProcessor] regress-gen-profile: runs-on: ubuntu-latest env: @@ -184,4 +184,4 @@ jobs: name: Build container run: ./bin/build_container - name: Generate extension PDF - run: ./do gen:profile[MockProfileRelease] + run: ./do gen:profile_release_pdf[Mock] diff --git a/.gitignore b/.gitignore index a9bdc6be4..ce0c1b31c 100644 --- a/.gitignore +++ b/.gitignore @@ -11,7 +11,9 @@ diag-ditaa-* arch/manual/isa/**/riscv-isa-manual gen +resolved_arch node_modules _site images +*.bak *.log diff --git a/.gitmodules b/.gitmodules index 7b74de08d..02151e5bd 100644 --- a/.gitmodules +++ b/.gitmodules @@ -7,3 +7,6 @@ [submodule "ext/riscv-isa-manual"] path = ext/riscv-isa-manual url = https://github.com/riscv/riscv-isa-manual +[submodule "ext/csc-riscv-isa-manual"] + path = ext/csc-riscv-isa-manual + url = https://github.com/RISC-V-Certification-Steering-Committee/riscv-isa-manual diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 7cbbcf5a9..c5c579177 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -46,12 +46,12 @@ repos: args: ["--schemafile", "schemas/ext_schema.json"] - id: check-jsonschema alias: check-jsonschema-cert-model - files: ^arch/certificate_model/.*\.(yaml|yml)$ - args: ["--schemafile", "schemas/cert_model_schema.json"] + files: ^arch/proc_cert_model/.*\.(yaml|yml)$ + args: ["--schemafile", "schemas/proc_cert_model_schema.json"] - id: check-jsonschema alias: check-jsonschema-cert-class - files: ^arch/certificate_class/.*\.(yaml|yml)$ - args: ["--schemafile", "schemas/cert_class_schema.json"] + files: ^arch/proc_cert_class/.*\.(yaml|yml)$ + args: ["--schemafile", "schemas/proc_cert_class_schema.json"] # Commenting because throwing errors and not sure this is complete yet # - id: check-jsonschema # alias: check-jsonschema-manual-version diff --git a/.vscode/launch.json b/.vscode/launch.json index 09d8dae10..3fa985893 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -3,19 +3,64 @@ "configurations": [ { "type": "rdbg", - "name": "MC100-32", + "name": "MC100-32-CRD", "request": "launch", "command": "bundle exec rake", - "script": "gen:cert_model_pdf[MC100-32]", + "script": "gen:proc_crd_pdf[MC100-32]", "args": [], "askParameters": false }, { "type": "rdbg", - "name": "MC200-32", + "name": "MC100-32-CTP", "request": "launch", "command": "bundle exec rake", - "script": "gen:cert_model_pdf[MC200-32]", + "script": "gen:proc_ctp_pdf[MC100-32]", + "args": [], + "askParameters": false + }, + { + "type": "rdbg", + "name": "MC200-32-CRD", + "request": "launch", + "command": "bundle exec rake", + "script": "gen:proc_crd_pdf[MC200-32]", + "args": [], + "askParameters": false + }, + { + "type": "rdbg", + "name": "MockCRD", + "request": "launch", + "command": "bundle exec rake", + "script": "gen:proc_crd_pdf[MockProcessor]", + "args": [], + "askParameters": false + }, + { + "type": "rdbg", + "name": "MockCTP", + "request": "launch", + "command": "bundle exec rake", + "script": "gen:proc_ctp_pdf[MockProcessor]", + "args": [], + "askParameters": false + }, + { + "type": "rdbg", + "name": "portfolios", + "request": "launch", + "command": "bundle exec rake", + "script": "portfolios", + "args": [], + "askParameters": false + }, + { + "type": "rdbg", + "name": "MockProfile", + "request": "launch", + "command": "bundle exec rake", + "script": "gen:profile_release_pdf[Mock]", "args": [], "askParameters": false }, @@ -24,7 +69,16 @@ "name": "RVA20", "request": "launch", "command": "bundle exec rake", - "script": "gen:profile[RVA20]", + "script": "gen:profile_release_pdf[RVA20]", + "args": [], + "askParameters": false + }, + { + "type": "rdbg", + "name": "Smoke test", + "request": "launch", + "command": "bundle exec rake", + "script": "test:smoke", "args": [], "askParameters": false } diff --git a/README.adoc b/README.adoc index bca01db3b..a38b721ae 100644 --- a/README.adoc +++ b/README.adoc @@ -8,11 +8,13 @@ The following artifacts have been generated from the top of the `main` branch: * https://riscv-software-src.github.io/riscv-unified-db/example_cfg/html/index.html[configuration-specific documentation] * https://riscv-software-src.github.io/riscv-unified-db/ruby/arch_def/index.html[Ruby API documentation (database interface)] * https://riscv-software-src.github.io/riscv-unified-db/ruby/idl/index.html[Ruby IDL API documentation (IDL compiler)] -* https://riscv-software-src.github.io/riscv-unified-db/pdfs/RVI20.pdf[RVI20 Profile Release] -* https://riscv-software-src.github.io/riscv-unified-db/pdfs/RVA20.pdf[RVA20 Profile Release] -* https://riscv-software-src.github.io/riscv-unified-db/pdfs/RVA22.pdf[RVA22 Profile Release] -* https://riscv-software-src.github.io/riscv-unified-db/pdfs/MC100-32.pdf[MC100-32 Certification Requirements Document] -* https://riscv-software-src.github.io/riscv-unified-db/pdfs/MC100-64.pdf[MC100-64 Certification Requirements Document] +* https://riscv-software-src.github.io/riscv-unified-db/pdfs/RVI20ProfileRelease.pdf[RVI20 Profile Release] +* https://riscv-software-src.github.io/riscv-unified-db/pdfs/RVA20ProfileRelease.pdf[RVA20 Profile Release] +* https://riscv-software-src.github.io/riscv-unified-db/pdfs/RVA22ProfileRelease.pdf[RVA22 Profile Release] +* https://riscv-software-src.github.io/riscv-unified-db/pdfs/RVA23ProfileRelease.pdf[RVA23 Profile Release] +* https://riscv-software-src.github.io/riscv-unified-db/pdfs/RVB23ProfileRelease.pdf[RVB23 Profile Release] +* https://riscv-software-src.github.io/riscv-unified-db/pdfs/MC100-32-CRD.pdf[MC100-32 Certification Requirements Document] +* https://riscv-software-src.github.io/riscv-unified-db/pdfs/MC100-64-CRD.pdf[MC100-64 Certification Requirements Document] == Overview diff --git a/Rakefile b/Rakefile index 19fb65ab9..fe77dd6b5 100644 --- a/Rakefile +++ b/Rakefile @@ -10,15 +10,21 @@ require "yard" require "minitest/test_task" require_relative $root / "lib" / "architecture" +require_relative $root / "lib" / "design" +require_relative $root / "lib" / "portfolio_design" +require_relative $root / "lib" / "proc_cert_design" directory "#{$root}/.stamps" +# Load and execute Rakefile for each backend. Dir.glob("#{$root}/backends/*/tasks.rake") do |rakefile| load rakefile end directory "#{$root}/.stamps" +# @param config_name [String] Name of configuration +# @return [ConfiguredArchitecture] def cfg_arch_for(config_name) Rake::Task["#{$root}/.stamps/resolve-#{config_name}.stamp"].invoke @@ -123,7 +129,7 @@ namespace :test do end task schema: "gen:resolved_arch" do puts "Checking arch files against schema.." - Architecture.new("#{$root}/resolved_arch").validate(show_progress: true) + Architecture.new("RISC-V Architecture", "#{$root}/resolved_arch").validate(show_progress: true) puts "All files validate against their schema" end task idl: ["gen:resolved_arch", "#{$root}/.stamps/resolve-rv32.stamp", "#{$root}/.stamps/resolve-rv64.stamp"] do @@ -295,10 +301,16 @@ namespace :test do These are basic but fast-running tests to check the database and tools DESC task :smoke do + puts "UPDATE: Starting test:smoke" + puts "UPDATE: Running test:idl_compiler" Rake::Task["test:idl_compiler"].invoke + puts "UPDATE: Running test:lib" Rake::Task["test:lib"].invoke + puts "UPDATE: Running test:schema" Rake::Task["test:schema"].invoke + puts "UPDATE: Running test:idl" Rake::Task["test:idl"].invoke + puts "UPDATE: Done test:smoke" end desc <<~DESC @@ -307,22 +319,32 @@ namespace :test do These tests must pass before a commit will be allowed in the main branch on GitHub DESC task :regress do + puts "UPDATE: Starting test:regress" Rake::Task["test:smoke"].invoke + puts "UPDATE: Running gen:html_manual MANUAL_NAME=isa VERSIONS=all" ENV["MANUAL_NAME"] = "isa" ENV["VERSIONS"] = "all" Rake::Task["gen:html_manual"].invoke + puts "UPDATE: Running gen:ext_pdf EXT=B VERSION=latest" ENV["EXT"] = "B" ENV["VERSION"] = "latest" Rake::Task["gen:ext_pdf"].invoke + puts "UPDATE: Running gen:html for generic_rv64" Rake::Task["gen:html"].invoke("generic_rv64") - Rake::Task["#{$root}/gen/certificate_doc/pdf/MockCertificateModel.pdf"].invoke - Rake::Task["#{$root}/gen/profile_doc/pdf/MockProfileRelease.pdf"].invoke + puts "UPDATE: Generating MockProcessor-CRD.pdf" + Rake::Task["#{$root}/gen/proc_crd/pdf/MockProcessor-CRD.pdf"].invoke - puts + puts "UPDATE: Generating MockProcessor-CTP.pdf" + Rake::Task["#{$root}/gen/proc_ctp/pdf/MockProcessor-CTP.pdf"].invoke + + puts "UPDATE: Generating MockProfileRelease.pdf" + Rake::Task["#{$root}/gen/profile/pdf/MockProfileRelease.pdf"].invoke + + puts "UPDATE: Done test:regress" puts "Regression test PASSED" end @@ -343,32 +365,36 @@ desc <<~DESC Generate all portfolio-based PDF artifacts (certificates and profiles) DESC task :portfolios do - portfolio_start_msg("MockCertificateModel") - Rake::Task["#{$root}/gen/certificate_doc/pdf/MockCertificateModel.pdf"].invoke + portfolio_start_msg("MockProcessor-CRD") + Rake::Task["#{$root}/gen/proc_crd/pdf/MockProcessor-CRD.pdf"].invoke + portfolio_start_msg("MockProcessor-CTP") + Rake::Task["#{$root}/gen/proc_ctp/pdf/MockProcessor-CTP.pdf"].invoke portfolio_start_msg("MockProfileRelease") - Rake::Task["#{$root}/gen/profile_doc/pdf/MockProfileRelease.pdf"].invoke - portfolio_start_msg("MC100-32") - Rake::Task["#{$root}/gen/certificate_doc/pdf/MC100-32.pdf"].invoke - portfolio_start_msg("MC100-64") - Rake::Task["#{$root}/gen/certificate_doc/pdf/MC100-64.pdf"].invoke - portfolio_start_msg("MC200-32") - Rake::Task["#{$root}/gen/certificate_doc/pdf/MC200-32.pdf"].invoke - portfolio_start_msg("MC200-64") - Rake::Task["#{$root}/gen/certificate_doc/pdf/MC200-64.pdf"].invoke - portfolio_start_msg("MC300-32") - Rake::Task["#{$root}/gen/certificate_doc/pdf/MC300-32.pdf"].invoke - portfolio_start_msg("MC300-64") - Rake::Task["#{$root}/gen/certificate_doc/pdf/MC300-64.pdf"].invoke - portfolio_start_msg("RVI20") - Rake::Task["#{$root}/gen/profile_doc/pdf/RVI20.pdf"].invoke - portfolio_start_msg("RVA20") - Rake::Task["#{$root}/gen/profile_doc/pdf/RVA20.pdf"].invoke - portfolio_start_msg("RVA22") - Rake::Task["#{$root}/gen/profile_doc/pdf/RVA22.pdf"].invoke - portfolio_start_msg("RVA23") - Rake::Task["#{$root}/gen/profile_doc/pdf/RVA23.pdf"].invoke - portfolio_start_msg("RVB23") - Rake::Task["#{$root}/gen/profile_doc/pdf/RVB23.pdf"].invoke + Rake::Task["#{$root}/gen/profile/pdf/MockProfileRelease.pdf"].invoke + portfolio_start_msg("MC100-32-CTP") + Rake::Task["#{$root}/gen/proc_ctp/pdf/MC100-32-CTP.pdf"].invoke + portfolio_start_msg("MC100-32-CRD") + Rake::Task["#{$root}/gen/proc_crd/pdf/MC100-32-CRD.pdf"].invoke + portfolio_start_msg("MC100-64-CRD") + Rake::Task["#{$root}/gen/proc_crd/pdf/MC100-64-CRD.pdf"].invoke + portfolio_start_msg("MC200-32-CRD") + Rake::Task["#{$root}/gen/proc_crd/pdf/MC200-32-CRD.pdf"].invoke + portfolio_start_msg("MC200-64-CRD") + Rake::Task["#{$root}/gen/proc_crd/pdf/MC200-64-CRD.pdf"].invoke + portfolio_start_msg("MC300-32-CRD") + Rake::Task["#{$root}/gen/proc_crd/pdf/MC300-32-CRD.pdf"].invoke + portfolio_start_msg("MC300-64-CRD") + Rake::Task["#{$root}/gen/proc_crd/pdf/MC300-64-CRD.pdf"].invoke + portfolio_start_msg("RVI20ProfileRelease") + Rake::Task["#{$root}/gen/profile/pdf/RVI20ProfileRelease.pdf"].invoke + portfolio_start_msg("RVA20ProfileRelease") + Rake::Task["#{$root}/gen/profile/pdf/RVA20ProfileRelease.pdf"].invoke + portfolio_start_msg("RVA22ProfileRelease") + Rake::Task["#{$root}/gen/profile/pdf/RVA22ProfileRelease.pdf"].invoke + portfolio_start_msg("RVA23ProfileRelease") + Rake::Task["#{$root}/gen/profile/pdf/RVA23ProfileRelease.pdf"].invoke + portfolio_start_msg("RVB23ProfileRelease") + Rake::Task["#{$root}/gen/profile/pdf/RVB23ProfileRelease.pdf"].invoke end def portfolio_start_msg(name) @@ -379,17 +405,25 @@ def portfolio_start_msg(name) puts "" end -# Shortcut targets for building profiles and certificates. -task "MockCertificateModel": "#{$root}/gen/certificate_doc/pdf/MockCertificateModel.pdf" -task "MC100-32": "#{$root}/gen/certificate_doc/pdf/MC100-32.pdf" -task "MC100-64": "#{$root}/gen/certificate_doc/pdf/MC100-64.pdf" -task "MC200-32": "#{$root}/gen/certificate_doc/pdf/MC200-32.pdf" -task "MC200-64": "#{$root}/gen/certificate_doc/pdf/MC200-64.pdf" -task "MC300-32": "#{$root}/gen/certificate_doc/pdf/MC300-32.pdf" -task "MC300-64": "#{$root}/gen/certificate_doc/pdf/MC300-64.pdf" -task "MockProfileRelease": "#{$root}/gen/profile_doc/pdf/MockProfileRelease.pdf" -task "RVI20": "#{$root}/gen/profile_doc/pdf/RVI20.pdf" -task "RVA20": "#{$root}/gen/profile_doc/pdf/RVA20.pdf" -task "RVA22": "#{$root}/gen/profile_doc/pdf/RVA22.pdf" -task "RVA23": "#{$root}/gen/profile_doc/pdf/RVA23.pdf" -task "RVB23": "#{$root}/gen/profile_doc/pdf/RVB23.pdf" +# Shortcut targets for building CRDs, CTPs, and Profile Releases. +task "MockCRD": "#{$root}/gen/proc_crd/pdf/MockProcessor-CRD.pdf" +task "MockProcessorCRD": "#{$root}/gen/proc_crd/pdf/MockProcessor-CRD.pdf" +task "MockCTP": "#{$root}/gen/proc_ctp/pdf/MockProcessor-CTP.pdf" +task "MockProcessorCTP": "#{$root}/gen/proc_ctp/pdf/MockProcessor-CTP.pdf" +task "MockCTP-HTML": "#{$root}/gen/proc_ctp/pdf/MockProcessor-CTP.html" +task "MockProcessorCTP-HTML": "#{$root}/gen/proc_ctp/pdf/MockProcessor-CTP.html" +task "MC100-32-CTP": "#{$root}/gen/proc_ctp/pdf/MC100-32-CTP.pdf" +task "MC100-32-CTP-HTML": "#{$root}/gen/proc_ctp/pdf/MC100-32-CTP.html" +task "MC100-32-CRD": "#{$root}/gen/proc_crd/pdf/MC100-32-CRD.pdf" +task "MC100-64-CRD": "#{$root}/gen/proc_crd/pdf/MC100-64-CRD.pdf" +task "MC200-32-CRD": "#{$root}/gen/proc_crd/pdf/MC200-32-CRD.pdf" +task "MC200-64-CRD": "#{$root}/gen/proc_crd/pdf/MC200-64-CRD.pdf" +task "MC300-32-CRD": "#{$root}/gen/proc_crd/pdf/MC300-32-CRD.pdf" +task "MC300-64-CRD": "#{$root}/gen/proc_crd/pdf/MC300-64-CRD.pdf" +task "MockProfile": "#{$root}/gen/profile/pdf/MockProfileRelease.pdf" +task "MockProfileRelease": "#{$root}/gen/profile/pdf/MockProfileRelease.pdf" +task "RVI20": "#{$root}/gen/profile/pdf/RVI20ProfileRelease.pdf" +task "RVA20": "#{$root}/gen/profile/pdf/RVA20ProfileRelease.pdf" +task "RVA22": "#{$root}/gen/profile/pdf/RVA22ProfileRelease.pdf" +task "RVA23": "#{$root}/gen/profile/pdf/RVA23ProfileRelease.pdf" +task "RVB23": "#{$root}/gen/profile/pdf/RVB23ProfileRelease.pdf" diff --git a/arch/certificate_class/MC.yaml b/arch/certificate_class/MC.yaml deleted file mode 100644 index ae5eb1977..000000000 --- a/arch/certificate_class/MC.yaml +++ /dev/null @@ -1,13 +0,0 @@ -# yaml-language-server: $schema=../../schemas/cert_class_schema.json - -$schema: cert_class_schema.json# -kind: Processor CRD -processor_kind: Microcontroller -name: MC -long_name: Microcontroller Class CRD - -introduction: | - The MC (Microcontroller Class) targets processors running low-level software on an RTOS or bare-metal. - -mandatory_priv_modes: - - M diff --git a/arch/certificate_class/MockCertificateClass.yaml b/arch/certificate_class/MockCertificateClass.yaml deleted file mode 100644 index e358b8684..000000000 --- a/arch/certificate_class/MockCertificateClass.yaml +++ /dev/null @@ -1,13 +0,0 @@ -# yaml-language-server: $schema=../../schemas/cert_class_schema.json - -$schema: cert_class_schema.json# -kind: Processor CRD -processor_kind: Apps Processor -name: MockCertificateClass -long_name: Mock Certificate Class Long Name - -introduction: | - Here's the Mock Certificate Class introduction. - -mandatory_priv_modes: - - M diff --git a/arch/certificate_model/MC100-64.yaml b/arch/certificate_model/MC100-64.yaml deleted file mode 100644 index bede0a7a0..000000000 --- a/arch/certificate_model/MC100-64.yaml +++ /dev/null @@ -1,20 +0,0 @@ -# yaml-language-server: $schema=../../schemas/cert_model_schema.json - -$schema: cert_model_schema.json# -kind: certificate model -name: MC100-64 -long_name: Basic 64-bit Microcontroller Certificate -class: - $ref: certificate_class/MC.yaml# - -$inherits: "certificate_model/MC100-32.yaml#" - -# XLEN used by rakefile -base: 64 - -extensions: - Sm: - parameters: - XLEN: - schema: - const: 64 diff --git a/arch/certificate_model/MC200-64.yaml b/arch/certificate_model/MC200-64.yaml deleted file mode 100644 index a4925bdb8..000000000 --- a/arch/certificate_model/MC200-64.yaml +++ /dev/null @@ -1,20 +0,0 @@ -# yaml-language-server: $schema=../../schemas/cert_model_schema.json - -$schema: cert_model_schema.json# -kind: certificate model -name: MC200-64 -long_name: Intermediate 64-bit Microcontroller Certificate -class: - $ref: certificate_class/MC.yaml# - -$inherits: "certificate_model/MC200-32.yaml#" - -# XLEN used by rakefile -base: 64 - -extensions: - Sm: - parameters: - XLEN: - schema: - const: 64 diff --git a/arch/certificate_model/MC300-64.yaml b/arch/certificate_model/MC300-64.yaml deleted file mode 100644 index 251b49782..000000000 --- a/arch/certificate_model/MC300-64.yaml +++ /dev/null @@ -1,20 +0,0 @@ -# yaml-language-server: $schema=../../schemas/cert_model_schema.json - -$schema: cert_model_schema.json# -kind: certificate model -name: MC300-64 -long_name: Advanced 64-bit Microcontroller Certificate -class: - $ref: certificate_class/MC.yaml# - -$inherits: "certificate_model/MC300-32.yaml#" - -# XLEN used by rakefile -base: 64 - -extensions: - Sm: - parameters: - XLEN: - schema: - const: 64 diff --git a/arch/csr/misa.yaml b/arch/csr/misa.yaml index 5b3674d80..c0ca0267d 100644 --- a/arch/csr/misa.yaml +++ b/arch/csr/misa.yaml @@ -137,6 +137,30 @@ fields: return (implemented?(ExtensionName::M) && MUTABLE_MISA_M) ? CsrFieldType::RW : CsrFieldType::RO; definedBy: M reset_value: 1 + cert_coverage_points: + - id: csr_field.misa.M.disabled + name: Disabling `misa.M` bit + description: What happens when you turn off `misa.M` + doc_links: + - manual:csr:misa:disabling-extension + cert_test_procedures: + - id: csr.misa.M.muldiv_with_M_on&off + name: Testing M + description: Execute with M on/off + coverage_points: [csr_field.misa.M.disabled] + steps: + - name: on + description: Turn on `misa.M` + - name: execute + description: Execute every in-scope multiply extension instruction + - name: check + description: Check that every multiply extension instruction works as normal + - name: off + description: Turn off `misa.M` + - name: execute + description: Execute every in-scope multiply extension instruction + - name: check + description: Check that every multiply extension instruction throws illegal instruction exception S: location: 19 description: | @@ -184,3 +208,21 @@ sw_read(): | (CSR[misa].C << 2) | (CSR[misa].B << 1) | CSR[misa].A); +cert_coverage_points: + - id: csr.misa.disabling_bits + name: Disabling `misa` bits + description: What happens when you turn off bits + doc_links: + - manual:csr:misa:disabling-extension +cert_test_procedures: + - id: csr.misa.off&on + name: Testing bits + description: Turn on/off each bit and see what happens + coverage_points: [csr.misa.disabling_bits] + steps: + - name: setup + description: Turn on all bits + - name: loop + description: Turn off each present bit invidually and try affected behaviors + - name: check + description: fail unless turning off bit disables extension as expected diff --git a/arch/ext/Sha.yaml b/arch/ext/Sha.yaml index e0dc46d05..91a1d6d08 100644 --- a/arch/ext/Sha.yaml +++ b/arch/ext/Sha.yaml @@ -44,13 +44,12 @@ versions: - version: "1.0.0" state: ratified ratification_date: null - requires: - allOf: - - H - - Ssstateen - - Shcounterenw - - Shvstvala - - Shtvala - - Shvstvecd - - Shvsatpa - - Shgatpa + implies: + - [H, "1.0.0"] + - [Ssstateen, "1.0.0"] + - [Shcounterenw, "1.0.0"] + - [Shvstvala, "1.0.0"] + - [Shtvala, "1.0.0"] + - [Shvstvecd, "1.0.0"] + - [Shvsatpa, "1.0.0"] + - [Shgatpa, "1.0.0"] diff --git a/arch/ext/Shcounterenw.yaml b/arch/ext/Shcounterenw.yaml new file mode 100644 index 000000000..208ab6a3a --- /dev/null +++ b/arch/ext/Shcounterenw.yaml @@ -0,0 +1,21 @@ +# yaml-language-server: $schema=../../schemas/ext_schema.json + +$schema: "ext_schema.json#" +kind: extension +name: Shcounterenw +long_name: Hypervisor counter enable +description: | + For any hpmcounter that is not read-only zero, the corresponding bit in `hcounteren` must be writable. + + [NOTE] + This extension was ratified with the RVA22 profiles. +type: privileged +versions: + - version: "1.0.0" + state: ratified + ratification_date: 2023-08 + url: https://drive.google.com/file/d/1KcjgbLM5L1ZKY8934aJl8aQwGlMz6Cbo/view?usp=drive_link + param_constraints: + HCOUNTENABLE_EN: + extra_validation: | + HPM_COUNTER_EN.each_with_index { |hpm_exists, idx| assert(!hpm_exists || HCOUNTENABLE_EN[idx]) } diff --git a/arch/ext/Shvsatpa.yaml b/arch/ext/Shvsatpa.yaml new file mode 100644 index 000000000..97ca45647 --- /dev/null +++ b/arch/ext/Shvsatpa.yaml @@ -0,0 +1,16 @@ +# yaml-language-server: $schema=../../schemas/ext_schema.json + +$schema: "ext_schema.json#" +kind: extension +name: Shvsatpa +long_name: vstap translation mode requirements +description: | + All translation modes supported in the `satp` CSR must be supported in the `vsatp` CSR. + + [NOTE] + This extension was ratified with the RVA22 profiles. +type: privileged +versions: + - version: "1.0.0" + state: ratified + ratification_date: null diff --git a/arch/ext/Xmock.yaml b/arch/ext/Xmock.yaml index db1c0e0b7..4b24014e2 100644 --- a/arch/ext/Xmock.yaml +++ b/arch/ext/Xmock.yaml @@ -154,3 +154,29 @@ params: type: boolean maxItems: 8 minItems: 8 +cert_coverage_points: + - id: ext.Xmock.cov1 + name: Mock coverage point 1 + description: Let's have fun with the `Xmock` extension + doc_links: + - manual:inst:mul:encoding + - udb:doc:inst:mock + - id: ext.Xmock.cov2 + name: Mock coverage point 2 + description: And some more fun! + doc_links: + - manual:csr:misa:disabling-extension +cert_test_procedures: + - id: ext.Xmock.my_first + name: My first procedure + description: Verify that when it rains in Spain, it rains mainly on the plains! + coverage_points: [ext.Xmock.cov1, ext.Xmock.cov2] + steps: + - name: wait for rain + description: First we need some rain + note: This is getting silly. Very unprofessional. + - name: measure rainfall + description: Get a bunch of buckets around Spain + note: Yup, pretty silly + - name: compare rainful + description: fail unless more rain on plains than other regions diff --git a/arch/inst/M/mul.yaml b/arch/inst/M/mul.yaml index b4187b368..18ec9eb8d 100644 --- a/arch/inst/M/mul.yaml +++ b/arch/inst/M/mul.yaml @@ -34,7 +34,13 @@ access: u: always vs: always vu: always -data_independent_timing: true +data_independent_timing: + true + # Want to add something like this to IDL code. + # [#idl:code:inst:mul:exception-illegal_instruction]# + # if (implemented?(ExtensionName::M) && (CSR[misa].M == 1'b0)) { + # raise (ExceptionCode::IllegalInstruction, mode(), $encoding); + # }# operation(): | if (implemented?(ExtensionName::M) && (CSR[misa].M == 1'b0)) { raise (ExceptionCode::IllegalInstruction, mode(), $encoding); @@ -63,3 +69,39 @@ sail(): | RETIRE_FAIL } } + +cert_coverage_points: + - id: inst.mul.encoding + name: Encoding + description: Encoding of `mul` instruction + doc_links: + - manual:inst:mul:encoding + - id: inst.mul.basic_op + name: Basic operation + description: Basic operation of `mul` instruction + doc_links: + - manual:inst:mul:operation + - id: inst.mul.ill_exc_misa_M_disabled + name: Illegal instruction exception when misa.M is 0 + description: | + An illegal instruction exception is raised when the instruction is executed + and `misa.M` is 0. + doc_links: + - manual:csr:misa:disabling-extension + +cert_test_procedures: + - id: inst.mul.encoding + name: Encoding + description: Verify the encoding of the `mul` instruction + coverage_points: [inst.mul.encoding] + steps: + - name: setup + description: Load a variety of known values into rs1 & rs2 with a variety of rs1/rs2/rd values. + - name: execution + description: Execute the `mul` instruction + - name: validation + description: Check each result in rd + - name: teardown + description: Clear the registers used for rd + note: | + Don't really need to clear the registers so this is a contrived example diff --git a/arch/inst/mock.yaml b/arch/inst/mock.yaml new file mode 100644 index 000000000..2ab811621 --- /dev/null +++ b/arch/inst/mock.yaml @@ -0,0 +1,95 @@ +# yaml-language-server: $schema=../../schemas/inst_schema.json + +$schema: "inst_schema.json#" +kind: instruction +name: mock +long_name: Mock Instruction (Just for testing UDB) +description: | + The mock instruction computes the value of PI to an infinite number of decimal places. + Okay, actually it performs the equivalent of the `mul` instruction. + + [NOTE] + Computing PI to an infinite number of decicial places is impossible, but hey, why not? + +definedBy: Xmock +assembly: xd, xs1, xs2 +encoding: + match: 0000001----------000-----0110011 + variables: + - name: rs2 + location: 24-20 + - name: rs1 + location: 19-15 + - name: rd + location: 11-7 +access: + s: always + u: always + vs: always + vu: always +data_independent_timing: true +operation(): | + #anchor("illegal-inst-exc-misa-disabled") { + if (implemented?(ExtensionName::M) && (CSR[misa].M == 1'b0)) { + raise (ExceptionCode::IllegalInstruction, mode(), $encoding); + } + #} + + XReg src1 = X[rs1]; + XReg src2 = X[rs2]; + + #anchor("calculation") { + X[rd] = (src1 * src2)[XLEN-1:0]; + #} + +sail(): | + { + if extension("M") | haveZmmul() then { + let rs1_val = X(rs1); + let rs2_val = X(rs2); + let rs1_int : int = if signed1 then signed(rs1_val) else unsigned(rs1_val); + let rs2_int : int = if signed2 then signed(rs2_val) else unsigned(rs2_val); + let result_wide = to_bits(2 * sizeof(xlen), rs1_int * rs2_int); + let result = if high + then result_wide[(2 * sizeof(xlen) - 1) .. sizeof(xlen)] + else result_wide[(sizeof(xlen) - 1) .. 0]; + X(rd) = result; + RETIRE_SUCCESS + } else { + handle_illegal(); + RETIRE_FAIL + } + } + +cert_coverage_points: + - id: inst.mock.encoding&basic_op + name: Encoding and basic operation + description: Encoding and basic operation for `mock` instruction + doc_links: + - manual:inst:mul:encoding + - udb:doc:inst:mock + - id: inst.mock.ill_exc_misa_M_disabled + name: Illegal instruction exception when misa.M is 0 + description: | + An illegal instruction exception is raised when the instruction is executed + and `misa.M` is 0. + doc_links: + - manual:csr:misa:disabling-extension + # - idl:code:inst:mock:illegal-inst-exc-misa-disabled + +cert_test_procedures: + - id: inst.mock.enc_and_basic + name: Encoding + description: Verify the encoding and basic operation of the `mock` instruction + coverage_points: [inst.mock.encoding&basic_op] + steps: + - name: setup + description: Load a variety of known values into rs1 & rs2 with a variety of rs1/rs2/rd values. + - name: execution + description: Execute the `mock` instruction + - name: validation + description: Check each result in rd + - name: teardown + description: Clear the registers used for rd + note: | + Don't really need to clear the registers so this is a contrived example diff --git a/arch/proc_cert_class/MC.yaml b/arch/proc_cert_class/MC.yaml new file mode 100644 index 000000000..2008a3a3c --- /dev/null +++ b/arch/proc_cert_class/MC.yaml @@ -0,0 +1,13 @@ +# yaml-language-server: $schema=../../schemas/proc_cert_class_schema.json + +$schema: proc_cert_class_schema.json# +kind: Processor Certificate Class +processor_kind: Microcontroller +name: MC +long_name: Microcontroller Processor Certificate Class + +introduction: | + The MC (Microcontroller Class) targets processors running low-level software on an RTOS or bare-metal. + +mandatory_priv_modes: + - M diff --git a/arch/proc_cert_class/MockProcessor.yaml b/arch/proc_cert_class/MockProcessor.yaml new file mode 100644 index 000000000..be5f67909 --- /dev/null +++ b/arch/proc_cert_class/MockProcessor.yaml @@ -0,0 +1,13 @@ +# yaml-language-server: $schema=../../schemas/proc_cert_class_schema.json + +$schema: proc_cert_class_schema.json# +kind: Processor Certificate Class +processor_kind: Apps Processor +name: MockProcessor +long_name: Mock Processor Certificate Class Long Name + +introduction: | + Here's the Mock Certificate Class introduction. + +mandatory_priv_modes: + - M diff --git a/arch/certificate_model/MC100-32.yaml b/arch/proc_cert_model/MC100-32.yaml similarity index 89% rename from arch/certificate_model/MC100-32.yaml rename to arch/proc_cert_model/MC100-32.yaml index a2a0d00db..63b5a7c70 100644 --- a/arch/certificate_model/MC100-32.yaml +++ b/arch/proc_cert_model/MC100-32.yaml @@ -1,11 +1,11 @@ -# yaml-language-server: $schema=../../schemas/cert_model_schema.json +# yaml-language-server: $schema=../../schemas/proc_cert_model_schema.json -$schema: cert_model_schema.json# -kind: certificate model +$schema: proc_cert_model_schema.json# +kind: Processor Certificate Model name: MC100-32 long_name: Basic 32-bit Microcontroller Certificate class: - $ref: certificate_class/MC.yaml# + $ref: proc_cert_class/MC.yaml# # Semantic versions within the model versions: @@ -14,7 +14,13 @@ versions: # XLEN used by rakefile base: 32 +# History of this certificate. revision_history: + - revision: "0.8.0" + date: "2025-01-19" + changes: + - Updated so that content can apply equally to all certificate-related documents + such as CRDs (Certification Requirement Documents) and CTPs (Certification Test Plans). - revision: "0.7.0" date: "2024-07-29" changes: @@ -68,10 +74,8 @@ revision_history: - Initial version introduction: | - The MC100 Processor CRD (Certification Requirements Document) defines the requirements - a processor implementation must meet in order to be eligible for the associated MC100 certificate. - MC100 is a basic RISC-V processor with minimal M-mode support and has 32-bit and 64-bit variants. - + The MC100 Processor Certificate targets basic RISC-V microcontrollers. + It supports either a 32-bit (MC100-32) or 64-bit (MC100-64) base ISA. MC100 is not intended for the smallest possible microcontrollers but rather for applications benefiting from a minimal but standardized microcontroller. It consists of: diff --git a/arch/proc_cert_model/MC100-64.yaml b/arch/proc_cert_model/MC100-64.yaml new file mode 100644 index 000000000..13c595d76 --- /dev/null +++ b/arch/proc_cert_model/MC100-64.yaml @@ -0,0 +1,20 @@ +# yaml-language-server: $schema=../../schemas/proc_cert_model_schema.json + +$schema: proc_cert_model_schema.json# +kind: Processor Certificate Model +name: MC100-64 +long_name: Basic 64-bit Microcontroller Certificate +class: + $ref: proc_cert_class/MC.yaml# + +$inherits: "proc_cert_model/MC100-32.yaml#" + +# XLEN used by rakefile +base: 64 + +extensions: + Sm: + parameters: + XLEN: + schema: + const: 64 diff --git a/arch/certificate_model/MC200-32.yaml b/arch/proc_cert_model/MC200-32.yaml similarity index 67% rename from arch/certificate_model/MC200-32.yaml rename to arch/proc_cert_model/MC200-32.yaml index ee2d1049a..f2b52c847 100644 --- a/arch/certificate_model/MC200-32.yaml +++ b/arch/proc_cert_model/MC200-32.yaml @@ -1,11 +1,11 @@ -# yaml-language-server: $schema=../../schemas/cert_model_schema.json +# yaml-language-server: $schema=../../schemas/proc_cert_model_schema.json -$schema: cert_model_schema.json# -kind: certificate model +$schema: proc_cert_model_schema.json# +kind: Processor Certificate Model name: MC200-32 long_name: Intermediate 32-bit Microcontroller Certificate class: - $ref: certificate_class/MC.yaml# + $ref: proc_cert_class/MC.yaml# # Semantic versions within the model versions: @@ -14,7 +14,7 @@ versions: # XLEN used by rakefile base: 32 -$inherits: "certificate_model/MC100-32.yaml#" +$inherits: "proc_cert_model/MC100-32.yaml#" revision_history: - revision: "0.1.0" @@ -23,7 +23,9 @@ revision_history: - First created introduction: | - MC200 is an intermedicate RISC-V microcontroller that adds the following mandatory extensions to the MC100-series: + The MC200 Processor Certificate targets intermediate RISC-V microcontrollers. + It supports either a 32-bit (MC200-32) or 64-bit (MC200-64) base ISA. + The MC200 adds the following mandatory extensions to the MC100: * U extension (User-mode privilege level) * Smpmp extension (M-mode PMP) diff --git a/arch/proc_cert_model/MC200-64.yaml b/arch/proc_cert_model/MC200-64.yaml new file mode 100644 index 000000000..c3e551c08 --- /dev/null +++ b/arch/proc_cert_model/MC200-64.yaml @@ -0,0 +1,20 @@ +# yaml-language-server: $schema=../../schemas/proc_cert_model_schema.json + +$schema: proc_cert_model_schema.json# +kind: Processor Certificate Model +name: MC200-64 +long_name: Intermediate 64-bit Microcontroller Certificate +class: + $ref: proc_cert_class/MC.yaml# + +$inherits: "proc_cert_model/MC200-32.yaml#" + +# XLEN used by rakefile +base: 64 + +extensions: + Sm: + parameters: + XLEN: + schema: + const: 64 diff --git a/arch/certificate_model/MC300-32.yaml b/arch/proc_cert_model/MC300-32.yaml similarity index 51% rename from arch/certificate_model/MC300-32.yaml rename to arch/proc_cert_model/MC300-32.yaml index 512dd0b61..ca52f44ec 100644 --- a/arch/certificate_model/MC300-32.yaml +++ b/arch/proc_cert_model/MC300-32.yaml @@ -1,11 +1,11 @@ -# yaml-language-server: $schema=../../schemas/cert_model_schema.json +# yaml-language-server: $schema=../../schemas/proc_cert_model_schema.json -$schema: cert_model_schema.json# -kind: certificate model +$schema: proc_cert_model_schema.json# +kind: Processor Certificate Model name: MC300-32 long_name: Advanced 32-bit Microcontroller Certificate class: - $ref: certificate_class/MC.yaml# + $ref: proc_cert_class/MC.yaml# # Semantic versions within the model versions: @@ -14,7 +14,7 @@ versions: # XLEN used by rakefile base: 32 -$inherits: "certificate_model/MC200-32.yaml#" +$inherits: "proc_cert_model/MC200-32.yaml#" revision_history: - revision: "0.1.0" @@ -23,7 +23,9 @@ revision_history: - First created introduction: | - MC300 is an advanced RISC-V microcontroller that adds the following mandatory extensions to the MC200-series: + The MC300 Processor Certificate targets advanced RISC-V microcontrollers. + It supports either a 32-bit (MC300-32) or 64-bit (MC300-64) base ISA. + The MC300 adds the following mandatory extensions to the MC200: * S extension (Supervisor-mode privilege level) * Sspmp extension (S-mode PMP, not ratified yet) diff --git a/arch/proc_cert_model/MC300-64.yaml b/arch/proc_cert_model/MC300-64.yaml new file mode 100644 index 000000000..72b7bd71e --- /dev/null +++ b/arch/proc_cert_model/MC300-64.yaml @@ -0,0 +1,20 @@ +# yaml-language-server: $schema=../../schemas/proc_cert_model_schema.json + +$schema: proc_cert_model_schema.json# +kind: Processor Certificate Model +name: MC300-64 +long_name: Advanced 64-bit Microcontroller Certificate +class: + $ref: proc_cert_class/MC.yaml# + +$inherits: "proc_cert_model/MC300-32.yaml#" + +# XLEN used by rakefile +base: 64 + +extensions: + Sm: + parameters: + XLEN: + schema: + const: 64 diff --git a/arch/certificate_model/MockCertificateModel.yaml b/arch/proc_cert_model/MockProcessor.yaml similarity index 88% rename from arch/certificate_model/MockCertificateModel.yaml rename to arch/proc_cert_model/MockProcessor.yaml index b9c072e70..be396ecf9 100644 --- a/arch/certificate_model/MockCertificateModel.yaml +++ b/arch/proc_cert_model/MockProcessor.yaml @@ -1,11 +1,11 @@ -# yaml-language-server: $schema=../../schemas/cert_model_schema.json +# yaml-language-server: $schema=../../schemas/proc_cert_model_schema.json -$schema: cert_model_schema.json# -kind: certificate model -name: MockCertificateModel -long_name: Mock Certificate Model Long Name +$schema: proc_cert_model_schema.json# +kind: Processor Certificate Model +name: MockProcessor +long_name: Mock Processor Certificate Model Long Name class: - $ref: certificate_class/MockCertificateClass.yaml# + $ref: proc_cert_class/MockProcessor.yaml# # XLEN used by rakefile base: 64 @@ -26,7 +26,7 @@ revision_history: - Also created to test CRDs introduction: | - Mock CRD introduction: + Mock Certificate Model introduction: * Hello * Bob! @@ -83,6 +83,8 @@ extensions: note: | Here's a multi-line note + for the C extension. + M: + presence: mandatory Zicsr: version: "~> 2.0" presence: mandatory @@ -169,18 +171,10 @@ extensions: CACHE_BLOCK_SIZE: schema: const: 64 - Zba: - presence: mandatory - version: "~> 1.0" - note: "Added these as mandatory to see if bug in profiles not listing instructions in appendix is here in CRD too." - Zbb: - presence: mandatory - version: "~> 1.0" - note: "Added these as mandatory to see if bug in profiles not listing instructions in appendix is here in CRD too." - Zbs: + B: presence: mandatory version: "~> 1.0" - note: "Added these as mandatory to see if bug in profiles not listing instructions in appendix is here in CRD too." + note: "Added this as mandatory to see if Zba, Zbb, and Zbs are included." requirement_groups: - name: Req-Grp-Any-XLEN diff --git a/arch/profile/MP-S-64.yaml b/arch/profile/MP-S-64.yaml index de2718e08..afa97f1b6 100644 --- a/arch/profile/MP-S-64.yaml +++ b/arch/profile/MP-S-64.yaml @@ -1,13 +1,13 @@ # yaml-language-server: $schema=../../schemas/profile_schema.json $schema: profile_schema.json# -kind: profile +kind: Profile name: MP-S-64 marketing_name: MockProfile 64-bit S-mode description: This is the Mock Profile Supervisor Mode description. mode: S base: 64 -release: { $ref: profile_release/MockProfileRelease.yaml# } +release: { $ref: profile_release/Mock.yaml# } contributors: - name: Micky Mouse email: micky@disney.com diff --git a/arch/profile/MP-U-64.yaml b/arch/profile/MP-U-64.yaml index 3273c323b..434dd100f 100644 --- a/arch/profile/MP-U-64.yaml +++ b/arch/profile/MP-U-64.yaml @@ -1,10 +1,10 @@ $schema: profile_schema.json# -kind: profile +kind: Profile name: MP-U-64 marketing_name: MockProfile 64-bit Unpriv mode: Unpriv base: 64 -release: { $ref: profile_release/MockProfileRelease.yaml# } +release: { $ref: profile_release/Mock.yaml# } extensions: A: presence: optional diff --git a/arch/profile/RVA20S64.yaml b/arch/profile/RVA20S64.yaml index 939b820f0..5f8e59c87 100644 --- a/arch/profile/RVA20S64.yaml +++ b/arch/profile/RVA20S64.yaml @@ -1,5 +1,5 @@ $schema: profile_schema.json# -kind: profile +kind: Profile name: RVA20S64 marketing_name: RVA20S64 mode: S diff --git a/arch/profile/RVA20U64.yaml b/arch/profile/RVA20U64.yaml index 47d1cfa4b..66088a8aa 100644 --- a/arch/profile/RVA20U64.yaml +++ b/arch/profile/RVA20U64.yaml @@ -1,5 +1,5 @@ $schema: profile_schema.json# -kind: profile +kind: Profile name: RVA20U64 marketing_name: RVA20U64 mode: Unpriv diff --git a/arch/profile/RVA22S64.yaml b/arch/profile/RVA22S64.yaml index e2de79e2a..60b3c39cc 100644 --- a/arch/profile/RVA22S64.yaml +++ b/arch/profile/RVA22S64.yaml @@ -1,5 +1,5 @@ $schema: profile_schema.json# -kind: profile +kind: Profile name: RVA22S64 marketing_name: RVA22S64 mode: S @@ -26,41 +26,6 @@ extensions: Svinval: presence: mandatory version: "~> 1.0" - Ssstateen: - presence: mandatory - version: "~> 1.0" - when: - implemented: H - note: | - Ssstateen is a new extension name introduced with RVA22. - Shvstvala: - presence: mandatory - version: "~> 1.0" - when: - implemented: H - note: | - Shvstvala is a new extension name introduced with RVA22. - Shtvala: - presence: mandatory - version: "~> 1.0" - when: - implemented: H - note: | - Shtvala is a new extension name introduced with RVA22. - Shvstvecd: - presence: mandatory - version: "~> 1.0" - when: - implemented: H - note: | - Shvstvecd is a new extension name introduced with RVA22. - Shgatpa: - presence: mandatory - version: "~> 1.0" - when: - implemented: H - note: | - Shgatpa is a new extension name introduced with RVA22. Sv57: presence: optional version: "~> 1.12" diff --git a/arch/profile/RVA22U64.yaml b/arch/profile/RVA22U64.yaml index 7e3b1c0f0..b30dae49b 100644 --- a/arch/profile/RVA22U64.yaml +++ b/arch/profile/RVA22U64.yaml @@ -1,5 +1,5 @@ $schema: profile_schema.json# -kind: profile +kind: Profile name: RVA22U64 marketing_name: RVA22U64 mode: Unpriv diff --git a/arch/profile/RVA23S64.yaml b/arch/profile/RVA23S64.yaml index 2484cafa9..cf0b50f0c 100644 --- a/arch/profile/RVA23S64.yaml +++ b/arch/profile/RVA23S64.yaml @@ -1,5 +1,5 @@ $schema: profile_schema.json# -kind: profile +kind: Profile name: RVA23S64 marketing_name: RVA23S64 mode: S diff --git a/arch/profile/RVA23U64.yaml b/arch/profile/RVA23U64.yaml index 00c9206bc..09d086259 100644 --- a/arch/profile/RVA23U64.yaml +++ b/arch/profile/RVA23U64.yaml @@ -1,5 +1,5 @@ $schema: profile_schema.json# -kind: profile +kind: Profile name: RVA23U64 marketing_name: RVA23U64 mode: Unpriv diff --git a/arch/profile/RVB23S64.yaml b/arch/profile/RVB23S64.yaml index 404096b7a..1a898dce1 100644 --- a/arch/profile/RVB23S64.yaml +++ b/arch/profile/RVB23S64.yaml @@ -1,5 +1,5 @@ $schema: profile_schema.json# -kind: profile +kind: Profile name: RVB23S64 marketing_name: RVB23S64 mode: S diff --git a/arch/profile/RVB23U64.yaml b/arch/profile/RVB23U64.yaml index b133ce78a..f9bae8faa 100644 --- a/arch/profile/RVB23U64.yaml +++ b/arch/profile/RVB23U64.yaml @@ -1,5 +1,5 @@ $schema: profile_schema.json# -kind: profile +kind: Profile name: RVB23U64 marketing_name: RVB23U64 mode: Unpriv diff --git a/arch/profile/RVI20U32.yaml b/arch/profile/RVI20U32.yaml index 4a1e17343..9c9b25e23 100644 --- a/arch/profile/RVI20U32.yaml +++ b/arch/profile/RVI20U32.yaml @@ -1,5 +1,5 @@ $schema: profile_schema.json# -kind: profile +kind: Profile name: RVI20U32 marketing_name: RVI20U32 mode: Unpriv diff --git a/arch/profile/RVI20U64.yaml b/arch/profile/RVI20U64.yaml index aa84d2e41..6d5adb983 100644 --- a/arch/profile/RVI20U64.yaml +++ b/arch/profile/RVI20U64.yaml @@ -1,5 +1,5 @@ $schema: profile_schema.json# -kind: profile +kind: Profile name: RVI20U64 $inherits: "profile/RVI20U32.yaml#" base: 64 diff --git a/arch/profile_class/MockProfileClass.yaml b/arch/profile_class/Mock.yaml similarity index 93% rename from arch/profile_class/MockProfileClass.yaml rename to arch/profile_class/Mock.yaml index 36da53ffa..037cda8c9 100644 --- a/arch/profile_class/MockProfileClass.yaml +++ b/arch/profile_class/Mock.yaml @@ -1,7 +1,7 @@ $schema: profile_class_schema.json# -kind: profile class +kind: Profile Class processor_kind: Microcontroller -name: MockProfileClass +name: Mock marketing_name: Mock Profile Class introduction: Here's the Mock Profile Class introduction. description: | diff --git a/arch/profile_class/RVA.yaml b/arch/profile_class/RVA.yaml index f90ddfe13..ccff3ecaa 100644 --- a/arch/profile_class/RVA.yaml +++ b/arch/profile_class/RVA.yaml @@ -1,7 +1,7 @@ # yaml-language-server: $schema=../../schemas/profile_class_schema.json $schema: profile_class_schema.json# -kind: profile class +kind: Profile Class processor_kind: Apps Processor name: RVA marketing_name: RVA diff --git a/arch/profile_class/RVB.yaml b/arch/profile_class/RVB.yaml index 3aa2786fd..223c6e0e9 100644 --- a/arch/profile_class/RVB.yaml +++ b/arch/profile_class/RVB.yaml @@ -1,5 +1,5 @@ $schema: profile_class_schema.json# -kind: profile class +kind: Profile Class processor_kind: Apps Processor name: RVB marketing_name: RVB diff --git a/arch/profile_class/RVI.yaml b/arch/profile_class/RVI.yaml index 7bcf2aad3..d90b4c866 100644 --- a/arch/profile_class/RVI.yaml +++ b/arch/profile_class/RVI.yaml @@ -1,5 +1,5 @@ $schema: profile_class_schema.json# -kind: profile class +kind: Profile Class processor_kind: Generic Unprivileged name: RVI marketing_name: RVI diff --git a/arch/profile_release/MockProfileRelease.yaml b/arch/profile_release/Mock.yaml similarity index 83% rename from arch/profile_release/MockProfileRelease.yaml rename to arch/profile_release/Mock.yaml index 8c4c63543..5d152071b 100644 --- a/arch/profile_release/MockProfileRelease.yaml +++ b/arch/profile_release/Mock.yaml @@ -1,8 +1,9 @@ $schema: profile_release_schema.json# -kind: profile release -name: MockProfileRelease -marketing_name: MockProfileRelease Marketing Name -class: MockProfileClass +kind: Profile Release +name: Mock +marketing_name: Mock Profile Release Marketing Name +class: + $ref: profile_class/Mock.yaml# release: 20 state: ratified # current status ["ratified", "development"] versions: diff --git a/arch/profile_release/RVA20.yaml b/arch/profile_release/RVA20.yaml index c524c091c..e5c4803fb 100644 --- a/arch/profile_release/RVA20.yaml +++ b/arch/profile_release/RVA20.yaml @@ -1,8 +1,9 @@ $schema: profile_release_schema.json# -kind: profile release +kind: Profile Release name: RVA20 marketing_name: RVA20 -class: RVA +class: + $ref: profile_class/RVA.yaml# release: 20 state: ratified # current status ["ratified", "development"] ratification_date: "2023-04-03" diff --git a/arch/profile_release/RVA22.yaml b/arch/profile_release/RVA22.yaml index 284ed4e78..659562724 100644 --- a/arch/profile_release/RVA22.yaml +++ b/arch/profile_release/RVA22.yaml @@ -1,8 +1,9 @@ $schema: profile_release_schema.json# -kind: profile release +kind: Profile Release name: RVA22 marketing_name: RVA22 -class: RVA +class: + $ref: profile_class/RVA.yaml# release: 22 state: ratified # current status ["ratified", "development"] ratification_date: "2023-04-03" diff --git a/arch/profile_release/RVA23.yaml b/arch/profile_release/RVA23.yaml index 102ad13b3..e7d7eec56 100644 --- a/arch/profile_release/RVA23.yaml +++ b/arch/profile_release/RVA23.yaml @@ -1,8 +1,9 @@ -$schema: profile_schema.json# -kind: profile +$schema: profile_release_schema.json# +kind: Profile Release name: RVA23 marketing_name: RVA23 -class: RVA +class: + $ref: profile_class/RVA.yaml# release: 23 state: ratified # current status ["ratified", "development"] ratification_date: "2023-04-03" diff --git a/arch/profile_release/RVB23.yaml b/arch/profile_release/RVB23.yaml index 98e453eac..a6a679aa5 100644 --- a/arch/profile_release/RVB23.yaml +++ b/arch/profile_release/RVB23.yaml @@ -1,8 +1,9 @@ -$schema: profile_schema.json# -kind: profile +$schema: profile_release_schema.json# +kind: Profile Release name: RVB23 marketing_name: RVB23 -class: RVB +class: + $ref: profile_class/RVB.yaml# release: 23 state: ratified # current status ["ratified", "development"] ratification_date: "2023-04-03" diff --git a/arch/profile_release/RVI20.yaml b/arch/profile_release/RVI20.yaml index 55a5a265d..eb66ccc9b 100644 --- a/arch/profile_release/RVI20.yaml +++ b/arch/profile_release/RVI20.yaml @@ -1,9 +1,10 @@ $schema: profile_release_schema.json# -kind: profile release +kind: Profile Release name: RVI20 marketing_name: RVI20 -class: RVI -release: 20 +class: + $ref: profile_class/RVI.yaml# +base: null state: ratified # current status ["ratified", "development"] ratification_date: "2023-04-03" diff --git a/backends/certificate_doc/tasks.rake b/backends/certificate_doc/tasks.rake deleted file mode 100644 index 31766c0cd..000000000 --- a/backends/certificate_doc/tasks.rake +++ /dev/null @@ -1,148 +0,0 @@ -# frozen_string_literal: true - -require "pathname" - -require "asciidoctor-pdf" -require "asciidoctor-diagram" - -require_relative "#{$lib}/idl/passes/gen_adoc" - -CERT_DOC_DIR = Pathname.new "#{$root}/backends/certificate_doc" - -Dir.glob("#{$root}/arch/certificate_model/*.yaml") do |f| - cert_model_name = File.basename(f, ".yaml") - cert_model_obj = YAML.load_file(f, permitted_classes: [Date]) - cert_class_name = File.basename(cert_model_obj['class']['$ref'].split("#")[0], ".yaml") - raise "Ill-formed certificate model file #{f}: missing 'class' field" if cert_model_obj['class'].nil? - - base = cert_model_obj["base"] - raise "Missing certificate model base" if base.nil? - - file "#{$root}/gen/certificate_doc/adoc/#{cert_model_name}.adoc" => [ - "#{$root}/arch/certificate_model/#{cert_model_name}.yaml", - "#{$root}/arch/certificate_class/#{cert_class_name}.yaml", - "#{CERT_DOC_DIR}/templates/certificate.adoc.erb", - __FILE__ - ] do |t| - puts "UPDATE: Creating bootstrap objects for #{cert_model_name}" - - # Create bootstrap ConfiguredArchitecture object which also creates and contains - # a PartialConfig object for the rv32/rv64 configuration. - bootstrap_cfg_arch = cfg_arch_for("rv#{base}") - - # Creates CertModel object for every certificate model in the database - # using rv32/rv64 PartialConfig object and then returns named CertModel object. - bootstrap_cert_model = bootstrap_cfg_arch.cert_model(cert_model_name) - raise "No certificate model named '#{cert_model_name}'" if bootstrap_cert_model.nil? - - puts "UPDATE: Creating real objects for #{cert_model_name}" - - # Use bootstrap CertModel to create a ConfiguredArchitecture for this CertModel - # to use instead of the the bootstrap one created based on the rv32/rv64 configuration. - cfg_arch = bootstrap_cert_model.to_cfg_arch - - # Use model-specific ConfiguredArchitecture to create CertModel objects again - # for every certificate model in the database and then return named CertModel object. - cert_model = cfg_arch.cert_model(cert_model_name) - - # Set globals for ERB template. - portfolio = cert_model - cert_class = cert_model.cert_class - portfolio = cert_model - portfolio_class = cert_class - - version = File.basename(t.name, '.adoc').split('-')[1..].join('-') - - erb = ERB.new(File.read("#{CERT_DOC_DIR}/templates/certificate.adoc.erb"), trim_mode: "-") - erb.filename = "#{CERT_DOC_DIR}/templates/certificate.adoc.erb" - - FileUtils.mkdir_p File.dirname(t.name) - - # Convert ERB to final ASCIIDOC. Note that this code is broken up into separate function calls - # each with a variable name to aid in running a command-line debugger on this code. - erb_result = erb.result(binding) - erb_result_monospace_converted_to_links = cfg_arch.find_replace_links(erb_result) - erb_result_with_links_added = cfg_arch.find_replace_links(erb_result_monospace_converted_to_links) - erb_result_with_links_resolved = AsciidocUtils.resolve_links(erb_result_with_links_added) - - File.write t.name, erb_result_with_links_resolved - puts "Generated adoc source at #{t.name}" - end - - file "#{$root}/gen/certificate_doc/pdf/#{cert_model_name}.pdf" => [ - "#{$root}/gen/certificate_doc/adoc/#{cert_model_name}.adoc" - ] do |t| - adoc_file = "#{$root}/gen/certificate_doc/adoc/#{cert_model_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/certificate_doc/html/#{cert_model_name}.html" => [ - "#{$root}/gen/certificate_doc/adoc/#{cert_model_name}.adoc" - ] do |t| - adoc_file = "#{$root}/gen/certificate_doc/adoc/#{cert_model_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 certificate documentation for a specific version as a PDF - - Required options: - cert_model_name - The key of the certification model under arch/certificate_model - DESC - task :cert_model_pdf, [:cert_model_name] do |_t, args| - if args[:cert_model_name].nil? - warn "Missing required option: 'cert_model_name'" - exit 1 - end - - unless File.exist?("#{$root}/arch/certificate_model/#{args[:cert_model_name]}.yaml") - warn "No certification model named '#{args[:cert_model_name]}' found in arch/certificate_model" - exit 1 - end - - Rake::Task["#{$root}/gen/certificate_doc/pdf/#{args[:cert_model_name]}.pdf"].invoke - end - - task :cert_model_html, [:cert_model_name] do |_t, args| - if args[:cert_model_name].nil? - warn "Missing required option: 'cert_model_name'" - exit 1 - end - - unless File.exist?("#{$root}/arch/certificate_model/#{args[:cert_model_name]}.yaml") - warn "No certification model named '#{args[:cert_model_name]}' found in arch/certificate_model" - exit 1 - end - - Rake::Task["#{$root}/gen/certificate_doc/html/#{args[:cert_model_name]}.html"].invoke - end -end diff --git a/backends/certificate_doc/templates/certificate.adoc.erb b/backends/certificate_doc/templates/certificate.adoc.erb deleted file mode 100644 index 4b695f722..000000000 --- a/backends/certificate_doc/templates/certificate.adoc.erb +++ /dev/null @@ -1,878 +0,0 @@ -// Number heading sections (e.g., 1.0, 1.1, etc.) -:sectnums: - -// Add a table of contents for HTML (and VSCode adoc preview) -:toc: left - -// Include headings up to 3 levels deep (don't know why 5 gives you this). -:toclevels: 5 - -// -// Stuff to generate nice wavedrom drawings of instruction and CSR fields -// -:wavedrom: <%= $root %>/node_modules/.bin/wavedrom-cli - -// TODO: needs to be changed -:imagesoutdir: images - -= <%= cert_model.name %> Processor Certification Requirements Document - -[Preface] -== Revision History - -History of documentation changes that eventually lead to releases. - -[cols="1,1,5"] -|=== -| Date | Revision | Changes - -<% cert_model.revision_history.each do |rev| -%> -| <%= rev.date %> -| <%= rev.revision %> -a| <% rev.changes.each do |change| %> -* <%= change %> -<% end -%> -<% end -%> -|=== - -[Preface] -== Typographic Conventions - -CSR field colors:: - -* Grey fields are reserved (WPRI) -* Green fields are present -* Red fields are defined by the RISC-V ISA but not present - -CSR field types:: - -[%autowidth] -|=== -| Abbreviation | Description - -<% CsrField::TYPE_DESC_MAP.each do |abbreviation, description| -%> -| <%= abbreviation %> -| <%= description %> -<% end -%> -|=== - -== Introduction - -<%= cert_model.introduction %> - -<%= cert_class.introduction %> - -=== What's a CRD? - -Certification Requirements Documents (CRDs) list requirements an implementation must meet -to obtain an associated RVI (RISC-V International) certificate. -CRDs are developed by the RVI CSC (Certification Steering Committee) organization in collaboration -with the RVI TSC (Technical Steering Committee) organization who creates RISC-V standards. - -The CRDs refer to and augment information provided in existing ratified RVI standards. - -There are a variety of certificates offered by RVI to accomodate the various RVI standards. -There are certificates for processors, non-processor system IP (e.g., IOMMU), -and system platforms (processor + system IP) hardware standards. -There are multiple classes of processor certificates available to accomodate the wide range of -RISC-V implementations from basic microcontrollers to advanced Applications-class processors. - -Each CRD has a list of mandatory behaviors along with a list of optional behaviors. -Note that not all behaviors allowed in RISC-V standards are supported by a particular CRD. - -=== CRD Naming Scheme - -CRDs have the following naming scheme: - - Format: [v] - -Where: - -* Left & right square braces denote optional. -* Less-than & greater-than signs just separate fields (i.e., they aren't present in the CRD name). -* identifies the type of RISC-V standard (processor, non-processor system IP, or platform) along with - any other information required to identify the variant of that standard. -* identifies a particular CRD release -** Format is [.[.]] -** Follows semantic versioning scheme (https://semver.org/) -** The release is updated when certification test changes are made that *could* cause a previously certified - implementation to now fail. - Examples are fixing a test bug, or increasing test coverage, or requiring a new version of a standard - A release of 0 is used for pre-release versions of a CRD and release versions start with 1. -** The release is updated when a CRD increases support for optional behaviors. - Examples are supporting for new optional standards or - supporting additional optional behaviors for standards already in a certificate. -** The release is updated when certification test changes are made that *can't* cause a previously certified - implementation to now fail. - Examples are test changes not designed to increase coverage or fixing a documentation typo. -** If omitted, defaults to v1.0.0 -** Examples: v1, v1.1, v2.3.1, 0.3.4 (pre-release) - -=== CRD Terminology - -.Requirement Types -[%autowidth] -|=== -| Term | Meaning - -| MANDATORY | You have to implement it to get a certificate and the certificate tests will cover it -| OPTIONAL | It's up to you if you implement or not. If you claim to implement it, certificate tests will cover it -| IN-SCOPE | Either MANDATORY or OPTIONAL -| OUT-OF-SCOPE | It's up to you if you implement or not. If you implement it, it won't be certified but make sure you don't mess up anything we are certifying. -| INCOMPATIBLE | If you implement it you won't get a certificate -|=== - -.Glossary -[%autowidth] -|=== -| Term | Meaning - -| CRD | Certification Requirements Document -| N/A | “Not Applicable” -| AKA | “Also Known As” -|=== - -=== Processor CRDs - -There are Processor CRDs for different classes of RISC-V processors. -These documents augment information in the related TSC Profile when available and/or other RVI standards documents -(e.g., Priv and Unpriv ISA manuals). -Only ratified extensions are candidates for certification. -This implies all custom extensions are also OUT-OF-SCOPE. - -==== Processor CRD Naming Scheme - -Processor CRD names have the following format: - - [<-base>] - -Where: - -* is MC for Microcontroller Class and AC for Apps-processor Class -* is 3-digit integer defined as follows: -** The hundreds's digit indicates the series -** The ten's digit identifies large differences in mandatory extensions (e.g., V, H) within the series -** The one's digit indentifies small/medium differences in mandatory extensions (e.g., Zicond, PMP) within the series -* is optional and is 32 for RV32I, 64 for RV64I, and 32E for RV32E -** If a CRD supports multiple bases and is omitted in a reference, it applies to all supported bases -** If a CRD only supports one base then is generally omitted - -[%autowidth] -|=== -| CRD | TSC Profile | Description - -| MC100-series | TBD | 32/64-bit minimal microcontroller that runs low-level software on an RTOS or bare-metal (no virtual memory) -| MC200-series | TBD | 32/64-bit intermediate microcontroller -| MC300-series | TBD | 32/64-bit advanced microcontroller -| AC100-series | RVB23 | 64-bit Apps-processor running Bespoke rich operating systems (e.g., Yocto Linux) -| AC200-series | RVA23 | 64-bit Apps-processor running standard rich operating systems (e.g., commercial Linux distributions, Android) -|=== - -==== CSR Field Terminology - -.Definition of CSR Fields -[%autowidth] -|=== -| Field Type | Read Value After Writing Illegal Value | Read Value Function Of | Illegal Instruction Exception | Priv ISA Manual Quote - -| WLRL | Any deterministic legal or illegal value | Value before write and illegal value written | Optional -| Implementations are permitted but not required to raise an illegal-instruction exception if an instruction attempts to write a non-supported value to a WLRL field. Implementations can return arbitrary bit patterns on the read of a WLRL field when the last write was of an illegal value, but the value returned should deterministically depend on the illegal written value and the value of the field prior to the write. -| WARL | Any deterministic legal value | Any architectural hart state | Prohibited -| Implementations will not raise an exception on writes of unsupported values to a WARL field. Implementations can return any legal value on the read of a WARL field when the last write was of an illegal value, but the legal value returned should deterministically depend on the illegal written value and the architectural state of the hart. -| WPRI | 0 | Nothing | Not specified -| Some whole read/write fields are reserved for future use. Implementations that do not furnish these fields must make them read-only zero. -|=== - -*WARL (Write Anything, Read Legal)*: - -The Priv ISA requires reads of WARL fields to return some implementation-dependent deterministic legal value -after the field is written with an illegal value. -Certifying such behaviors is expensive and provides low value for a certificate since software can't rely -on a particular behavior from one implementation to another. - -Processor CRDs define writes to WARL fields of illegal values to be OUT-OF-SCOPE unless otherwise stated -(i.e., certification tests will only ever write legal values to WARL fields except for the special cases listed below). -When not OUT-OF-SCOPE, the required behavior is defined as this might be more constrained in implementations than -in the standard. - -The following special cases for WARL are supported when explicitly listed in the corresponding CRD CSR field requirements: - -1. Probing for Field Width - -* Some WARL fields are variable length such as the ASID field in the virtual memory extension. -* Here's the algorithm recommended to discover the ASID width: -** The number of implemented ASID bits, termed ASIDLEN, may be determined by writing one to every bit position in - the ASID field, then reading back the value in the satp CSR to see which bit positions in the ASID field hold a one. -* The RVCP-provided certification materials (certification tests, certification reference models) can map writes of - illegal values to the ASID field to the corresponding read value as long as they are provided the ASIDLEN value - for an implementation. - -2. Probing for Options - -* E.g., Writable misa bits - -3. Allowed values are a function of extension presence and/or their parameters - -* E.g., satp.mode legal write values - -*WLRL (Write Legal, Read Legal)*: - -The Priv ISA requires reads of WLRL fields to return some implementation-dependent deterministic arbitrary value -after the field is written with an illegal value. -Certifying such behaviors is expensive and provides low value for a certificate since software can't rely -on a particular behavior. -Processor CRDs define writes to WLRL fields of illegal values to be OUT-OF-SCOPE unless otherwise stated -(i.e., certification tests will only ever write legal values to WLRL fields). - -*WPRI (Write Preserve, Read Ignore)*: - -The Priv ISA requires reads of WPRI fields to return a value of 0. -Such WPRI fields are always unimplemented by definition. -Certification tests are aware of which fields in the CSRs are WPRI and normally write them with 0 but will -also write them with ~0 (all ones) and ensure that reads return 0 in both cases. -It is OUT-OF-SCOPE for certification tests to write all possible values of WPRI fields -(especially if they are more than just a few bits) and certification tests aren't designed to be comprehensive -verification test suites anyways. - -=== Related Specifications - -[cols="2,2,3,3,3"] -|=== -| Certificate Model | TSC Profile | Unpriv ISA Manual | Priv ISA Manual | Debug Manual - -| <%= cert_model.name %> -| <%= cert_model.tsc_profile.nil? ? "No profile" : cert_model.tsc_profile.marketing_name %> -| <%= cert_model.unpriv_isa_manual_revision %> -| <%= cert_model.priv_isa_manual_revision %> -| <%= cert_model.debug_manual_revision %> -|=== - -=== Privileged Modes - -|=== -| M | S | U | VS | VU - -| <% if cert_class.mandatory_priv_modes.include?('M') -%> MANDATORY <% else -%> OUT-OF-SCOPE <% end -%> -| <% if cert_class.mandatory_priv_modes.include?('S') -%> MANDATORY <% else -%> OUT-OF-SCOPE <% end -%> -| <% if cert_class.mandatory_priv_modes.include?('U') -%> MANDATORY <% else -%> OUT-OF-SCOPE <% end -%> -| <% if cert_class.mandatory_priv_modes.include?('VS') -%> MANDATORY <% else -%> OUT-OF-SCOPE <% end -%> -| <% if cert_class.mandatory_priv_modes.include?('VU') -%> MANDATORY <% else -%> OUT-OF-SCOPE <% end -%> - -|=== - -<<< -== Extensions - -Any RISC-V extensions not listed in this section are OUT-OF-SCOPE. -The <%= cert_model.name %> certificate doesn't cover their behaviors. - -<% ExtensionPresence.presence_types_obj.each do |presence_obj| -%> - -=== <%= presence_obj.to_s.capitalize %> Extensions - -<% ext_reqs = cert_model.in_scope_ext_reqs(presence_obj) -%> -<% if ext_reqs.empty? -%> -None -<% else -%> -[%autowidth] -|=== -| Requirement ID | Extension | Version | Long Name | Note - -<% ext_reqs.sort.each do |ext_req| -%> -<% ext = cfg_arch.extension(ext_req.name) -%> -| <%= ext_req.req_id %> -| <-def,<%= ext_req.name %>>> -| <%= ext_req.requirement_specs.map(&:to_s).join(", ") %> -| <%= ext.nil? ? "" : ext.long_name %> -| <%= ext_req.note.nil? ? "" : ext_req.note %> -<% end # each ext_req -%> -|=== -<% end # if empty ext_reqs -%> - -<% cert_model.extra_notes_for_presence(presence_obj)&.each do |extra_note| -%> -NOTE: <%= extra_note.text %> - -<% end # each extra_note -%> - -<% end # each possible presence -%> - -<% unless cert_model.recommendations.empty? -%> -=== Recommendations - -Recommendations are not strictly mandated but are included to guide implementers making design choices. - -<% cert_model.recommendations.each do |recommendation| -%> -<%= recommendation.text %> -<% end # each recommendation -%> -<% end # unless recommendations empty -%> - -<<< -== Implementation-dependencies - -RISC-V standards support many implementation-defined parameters. In many cases, there -are no names associated with these parameters. Names are defined in this section when -not provided in the associated standard. - -=== IN-SCOPE Parameters - -These implementation-dependent options defined by MANDATORY or OPTIONAL extensions are IN-SCOPE. -An implementation must abide by the "Allowed Value(s)" to obtain a certificate. -If the "Allowed Value(s)" is "Any" then any value allowed by the type is acceptable. - -<% if cert_model.all_in_scope_ext_params.empty? -%> -None -<% else -%> -[cols="4,2,1,1,2"] -|=== -| Parameter | Type | Allowed Value(s) | Extension(s) | Note - -<% cert_model.all_in_scope_ext_params.sort.each do |in_scope_ext_param| -%> -<% param = in_scope_ext_param.param -%> -<% exts = cert_model.all_in_scope_exts_with_param(param) -%> -| <%= param.name_potentially_with_link(exts) %> -| <%= param.schema_type %> -| <%= in_scope_ext_param.allowed_values %> -| <% exts.sort.each do |ext| -%><-param-<%= param.name %>-def,<%= ext.name %>>> <% end # do ext -%> -a| <%= in_scope_ext_param.note %> -<% end # do -%> -|=== -<% end # if table -%> - -=== OUT-OF-SCOPE Parameters - -These implementation-dependent options defined by MANDATORY or OPTIONAL extensions are OUT-OF-SCOPE. -There are no restrictions on their values for certification purposes because the certificate -doesn't cover the behavior of the associated RISC-V standard as a function of these parameters. - -<% if cert_model.all_out_of_scope_params.empty? -%> -None -<% else -%> -[%autowidth] -|=== -| Parameters | Type | Extension(s) - -<% cert_model.all_out_of_scope_params.sort.each do |param| -%> -<% exts = cert_model.all_in_scope_exts_without_param(param) -%> -| <%= param.name_potentially_with_link(exts) %> -| <%= param.schema_type %> -| <% exts.sort.each do |ext| -%><-param-<%= param.name %>-def,<%= ext.name %>>> <% end # do ext -%> - -<% end # do -%> -|=== -<% end # if table -%> - -== Traps - -RISC-V supports both synchronous exceptions and asynchronous interrupts. -TODO: List only traps that exist in this certificate model (currently lists all possible in present extensions). -See https://github.com/riscv-software-src/riscv-unified-db/issues/291 and https://github.com/riscv-software-src/riscv-unified-db/issues/324 -TODO: Show traps per privilege mode - -=== Synchronous Exceptions - -|=== -| `xcause.CODE` CSR Field Value | Name -<% cfg_arch.exception_codes.sort_by{ |code| code.num }.each do |code| -%> -| <%= code.num %> | <%= code.name %> -<% end -%> -|=== - -=== Asynchronous Interrupts - -|=== -| `xcause.CODE` CSR Field Value | Name -<% cfg_arch.interrupt_codes.sort_by{ |code| code.num }.each do |code| -%> -| <%= code.num %> | <%= code.name %> -<% end -%> -|=== - -== Instruction Summary - -TODO: List only instructions that exist in this certificate model. -Currently lists all possible in present extensions so the I extension is providing both RV32I and RV64I instructions. -See https://github.com/riscv-software-src/riscv-unified-db/issues/291 and https://github.com/riscv-software-src/riscv-unified-db/issues/324 - -<% - insts = cert_model.in_scope_extensions.map { |ext_cert_model| ext_cert_model.instructions }.flatten.uniq - insts.sort_by!(&:name) --%> - -[%autowidth] -|=== -| Name | Long Name - -<% portfolio.in_scope_instructions.each do |inst| -%> -| <%= link_to_inst(inst.name) %> -| <%= inst.long_name %> -<% end # do -%> -|=== - -== CSR Summary - -<% - csrs = cert_model.in_scope_ext_reqs.map { |ext_req| ext_req.csrs }.flatten.uniq --%> - -=== By Name - -[%autowidth] -|=== -| Name | Long Name | Address | Mode | Primary Extension - -<% csrs.sort_by!(&:name).each do |csr| -%> -| <-def,<%= csr.name %>>> -| <%= csr.long_name %> -| <%= "0x#{csr.address.to_s(16)}" %> -| <%= csr.priv_mode %> -| <%= csr.primary_defined_by %> -<% end # do -%> -|=== - -=== By Address - -[%autowidth] -|=== -| Address | Mode | Name | Long Name | Primary Extension - -<% csrs.sort_by!(&:address).each do |csr| -%> -| <%= "0x#{csr.address.to_s(16)}" %> -| <%= csr.priv_mode %> -| <-def,<%= csr.name %>>> -| <%= csr.long_name %> -| <%= csr.primary_defined_by %> -<% end # do -%> -|=== - -<% unless cert_model.requirement_groups.empty? -%> -== Additional Requirements - -This section contains requirements in addition to those already specified related to extensions and parameters. -These additional requirements are organized as groups of related requirements. - -<% cert_model.requirement_groups.each do |group| -%> -=== <%= group.name %> - -<%= group.description %> - -<% unless group.when.nil? -%> -[IMPORTANT] -<%= group.name %> requirements only apply when <%= group.when_pretty %>. -<% end -%> - -[%autowidth] -|=== -| Req Number | Description - -<% group.requirements.each do |req| -%> -| <%= req.name %> -a| <%= req.description %> -<% unless req.when.nil? -%> -[IMPORTANT] -Requirement <%= req.name %> only apply when <%= req.when_pretty %>. -<% end -%> -<% end -%> -|=== - -<% end -%> -<% end # unless requirement_groups.empty? -%> - -<<< -[appendix] -== Extension Details -<% cert_model.in_scope_ext_reqs.sort.each do |ext_req| -%> -<% ext = cfg_arch.extension(ext_req.name) -%> - -[[ext-<%= ext_req.name %>-def]] -=== Extension <%= ext_req.name %> + -<%= ext.nil? ? "" : "*Long Name*: " + ext.long_name + " +" %> - -*Version Requirement*: <%= ext_req.requirement_specs.map(&:to_s).join(", ") %> + - -<% ext.versions.each do |v| -%> -<%= v.version_spec %>:: - State::: - <%= v.state %> - <% if v.state == "ratified" -%> - Ratification date::: - <%= v.ratification_date %> - <% end # if %> - <% if v.changes.size > 0 -%> - Changes::: - - <% v.changes.each do |c| -%> - * <%= c %> - <% end -%> - - <% end -%> - <% unless v.url.nil? -%> - Ratification document::: - <%= v.url %> - <% end -%> - <% if v.implications.size > 0 -%> - Implies::: - <% v.implications.each do |i| -%> - * `<%= i.name %>` version <%= i.version_spec %> - <% end -%> - <% end -%> -<% end -%> - -==== Synopsis - -:leveloffset: +3 - -<%= ext.description %> - -:leveloffset: -3 - -<% unless ext_req.note.nil? -%> -[NOTE] --- -<%= ext_req.note %> --- -<% end -%> - -// TODO: GitHub issue 92: Use version specified by each profile. -<% insts = cfg_arch.instructions.select { |i| i.defined_by?(ext.min_version) } -%> -<% unless insts.empty? -%> -==== Instructions - -The following instructions are added by this extension: - -[cols="1,3"] -|=== -<% insts.sort.each do |inst| -%> -| <%= link_to_inst(inst.name) %> -| *<%= inst.long_name %>* -<% end -%> -|=== -<% end -%> - -<% unless cert_model.in_scope_ext_params(ext_req).empty? -%> -==== IN-SCOPE Parameters - -<% cert_model.in_scope_ext_params(ext_req).sort.each do |ext_param| -%> -[[ext-<%= ext_req.name %>-param-<%= ext_param.name %>-def]] -<%= ext_param.name %> ⇒ <%= ext_param.param.schema_type %>:: -+ --- -<%= ext_param.param.desc %> --- -<% end # do ext_param -%> -<% end # unless table -%> - -<% unless cert_model.out_of_scope_params(ext_req.name).empty? -%> -==== OUT-OF-SCOPE Parameters - -<% cert_model.out_of_scope_params(ext_req.name).sort.each do |param| -%> -[[ext-<%= ext_req.name %>-param-<%= param.name %>-def]] -<%= param.name %> ⇒ <%= param.schema_type %>:: -+ --- -<%= param.desc %> --- -<% end # do param -%> -<% end # unless table -%> -<% end # do ext_req -%> - -<<< -[appendix] -== Instruction Details - -<% portfolio.in_scope_instructions.each do |inst| -%> -<<< -<%= anchor_for_inst(inst.name) %> -=== <%= inst.name %> - -*<%= inst.long_name %>* - -This instruction is defined by: - -<%= inst.defined_by_condition.to_asciidoc %> - -==== Encoding - -<% if inst.multi_encoding? -%> -[NOTE] -This instruction has different encodings in RV32 and RV64. - -==== -RV32:: -+ -[wavedrom, ,svg,subs='attributes',width="100%"] -.... -<%= JSON.dump inst.wavedrom_desc(32) %> -.... - -RV64:: -+ -[wavedrom, ,svg,subs='attributes',width="100%"] -.... -<%= JSON.dump inst.wavedrom_desc(64) %> -.... -==== -<% else -%> -[wavedrom, ,svg,subs='attributes',width="100%"] -.... -<%= JSON.dump inst.wavedrom_desc(inst.base.nil? ? 32 : inst.base) %> -.... -<% end -%> - -==== Synopsis - -<%= inst.description %> - -==== Access -<% if cert_model.in_scope_extensions.any? { |e| e.name == "H" } -%> -[cols="^,^,^,^,^"] -<% else -%> -[cols="^,^,^"] -<% end -%> -|=== -| M | <% if cert_model.in_scope_extensions.any? { |e| e.name == "H" } -%>HS<% else -%>S<% end -%> | U <% if cert_model.in_scope_extensions.any? { |e| e.name == "H" } -%> | VS | VU <% end -%> - -| [.access-always]#Always# -| [.access-<%=inst.access['s']%>]#<%= inst.access['s'].capitalize %># -| [.access-<%=inst.access['u']%>]#<%= inst.access['u'].capitalize %># -<% if cert_model.in_scope_extensions.any? { |e| e.name == "H" } %> -| [.access-<%=inst.access['vs']%>]#<%= inst.access['vs'].capitalize %># -| [.access-<%=inst.access['vu']%>]#<%= inst.access['vu'].capitalize %># -<% end %> -|=== - -<% if inst.access_detail? -%> -<%= inst.access_detail %> -<% end -%> - -==== Decode Variables - -<% if inst.multi_encoding? -%> -==== -RV32:: -+ -[source.idl] ----- -<% inst.decode_variables(32).each do |d| -%> -<%= d.sext? ? 'signed ' : '' %>Bits<<%= d.size %>> <%= d.name %> = <%= d.extract %>; -<% end -%> ----- - -RV64:: -+ -[source,idl] ----- -<% inst.decode_variables(64).each do |d| -%> -<%= d.sext? ? 'signed ' : '' %>Bits<<%= d.size %>> <%= d.name %> = <%= d.extract %>; -<% end -%> ----- -==== -<% else -%> -[source,idl] ----- -<% inst.decode_variables(inst.base.nil? ? 32 : inst.base).each do |d| -%> -<%= d.sext? ? 'signed ' : '' %>Bits<<%= d.size %>> <%= d.name %> = <%= d.extract %>; -<% end -%> ----- -<% end -%> - -==== Execution - -<% xlens = inst.base.nil? ? [32, 64] : [inst.base] -%> - -<% if inst.key?("operation()") -%> -[source,idl,subs="specialchars,macros"] ----- -<%= inst.operation_ast(cfg_arch.symtab).gen_adoc %> ----- -<% end -%> - -==== Exceptions - -<%- exception_list = inst.reachable_exceptions_str(cfg_arch.symtab) -%> -<%- if exception_list.empty? -%> -This instruction does not generate synchronous exceptions. -<%- else -%> -This instruction may result in the following synchronous exceptions: - - <%- exception_list.sort.each do |etype| -%> - * <%= etype %> - <%- end -%> - -<%- end -%> - - -<% end -%> - -<<< -[appendix] -== CSR Details - -<% - csrs = cert_model.in_scope_ext_reqs.map { |ext_req| ext_req.csrs }.flatten.uniq - csrs.sort_by!(&:name) --%> - -<% csrs.each do |csr| -%> -<<< -[[csr-<%= csr.name %>-def]] -=== <%= csr.name %> - -*<%= csr.long_name %>* - -<% unless csr.base.nil? -%> -[NOTE] --- -`<%= csr.name %>` is only defined in RV<%= csr.base %>. --- -<% end -%> - -<%= csr.description %> - -==== Attributes -[%autowidth] -|=== -h| CSR Address | <%= "0x#{csr.address.to_s(16)}" %> -<% if csr.priv_mode == 'VS' -%> -h| Virtual CSR Address | <%= "0x#{csr.virtual_address.to_s(16)}" %> -<% end -%> -h| Defining extension a| <%= csr.defined_by_condition.to_asciidoc %> -<% if csr.dynamic_length?(cfg_arch) -%> -h| Length | <%= csr.length_pretty(cfg_arch) %> -<% else -%> -h| Length | <%= csr.length_pretty(cfg_arch) %> -<% end -%> -h| Privilege Mode | <%= csr.priv_mode %> -|=== - - -==== Format -<% unless csr.dynamic_length?(cfg_arch) || csr.implemented_fields(cfg_arch).any? { |f| f.dynamic_location?(cfg_arch) } -%> -<%# CSR has a known static length, so there is only one format to display -%> -.<%= csr.name %> format -[wavedrom, ,svg,subs='attributes',width="100%"] -.... -<%= JSON.dump csr.wavedrom_desc(cfg_arch, csr.base.nil? ? 32 : csr.base, optional_type: 2) %> -.... -<% else -%> -<%# CSR has a dynamic length, or a field has a dynamic location, - so there is more than one format to display -%> -This CSR format changes dynamically with XLEN. - -.<%= csr.name %> Format when <%= csr.length_cond32 %> -[wavedrom, ,svg,subs='attributes',width="100%"] -.... -<%= JSON.dump csr.wavedrom_desc(cfg_arch, 32, optional_type: 2) %> -.... - -.<%= csr.name %> Format when <%= csr.length_cond64 %> -[wavedrom, ,svg,subs='attributes',width="100%"] -.... -<%= JSON.dump csr.wavedrom_desc(cfg_arch, 64, optional_type: 2) %> -.... - - -<% end # unless dynamic length -%> - -==== Field Summary - -// use @ as a separator since IDL code can contain | -[%autowidth,separator=@,float="center",align="center",cols="^,<,<,<",options="header",role="stretch"] -|=== -@ Name @ Location @ Type @ Reset Value - -<%- csr.implemented_fields(cfg_arch).each do |field| -%> -@ xref:<%=csr.name%>-<%=field.name%>-def[`<%= field.name %>`] -a@ -<%- if field.dynamic_location?(cfg_arch) -%> - -[when,"<%= field.location_cond32 %>"] --- -<%= field.location_pretty(cfg_arch, 32) %> --- - -[when,"<%= field.location_cond64 %>"] --- -<%= field.location_pretty(cfg_arch, 64) %> --- - -<%- else -%> -<%= field.location_pretty(cfg_arch) %> -<%- end -%> -a@ - --- -<%= field.type_pretty(cfg_arch.symtab) %> --- - -a@ - --- -<%= field.reset_value_pretty(cfg_arch) %> --- - -<%- end -%> -|=== - -==== Fields - -<%- if csr.implemented_fields(cfg_arch).empty? -%> -This CSR has no fields. However, it must still exist (not cause an `Illegal Instruction` trap) and always return zero on a read. -<%- else -%> - -<%- csr.implemented_fields(cfg_arch).each do |field| -%> -[[<%=csr.name%>-<%=field.name%>-def]] -===== `<%= field.name %>` - -<%- if !field.defined_in_all_bases? -%> -IMPORTANT: <%= field.name %> is only defined in <%= field.base32_only? ? "RV32" : "RV64" %> (`<%= field.base32_only? ? field.location_cond32 : field.location_cond64 %>`) -<%- end -%> - -**** -Location:: -<%= field.location_pretty(cfg_arch) %> - -Description:: -<%= field.description.gsub("\n", " +\n") %> - -Type:: -<%= field.type_pretty(cfg_arch.symtab) %> - -Reset value:: -<%= field.reset_value_pretty(cfg_arch) %> - -**** - -<%- end -%> -<%- end -%> - -<%- if csr.implemented_fields(cfg_arch).map(&:has_custom_sw_write?).any? -%> -==== Software write - -This CSR may store a value that is different from what software attempts to write. - -When a software write occurs (_e.g._, through `csrrw`), the following determines the -written value: - -[idl] ----- -<%- csr.implemented_fields(cfg_arch).each do |field| -%> -<%- if field.has_custom_sw_write? -%> -<%= field.name %> = <%= field["sw_write(csr_value)"] %> -<%- else -%> -<%= field.name %> = csr_value.<%= field.name %> -<%- end -%> -<%- end -%> ----- -<%- end -%> - -<%- if csr.has_custom_sw_read? -%> -==== Software read - -This CSR may return a value that is different from what is stored in hardware. - -[source,idl,subs="specialchars,macros"] ----- -<%= csr.sw_read_ast(cfg_arch.symtab).gen_adoc %> ----- -<%- end -%> - -<% end # do csrs -%> diff --git a/backends/cfg_html_doc/adoc_gen.rake b/backends/cfg_html_doc/adoc_gen.rake index 527927182..f65a33a27 100644 --- a/backends/cfg_html_doc/adoc_gen.rake +++ b/backends/cfg_html_doc/adoc_gen.rake @@ -8,6 +8,7 @@ require "ruby-prof" [ "#{CFG_HTML_DOC_DIR}/templates/#{type}.adoc.erb", "#{$root}/lib/cfg_arch.rb", + "#{$root}/lib/design.rb", "#{$root}/lib/idl/passes/gen_adoc.rb", __FILE__, "#{$root}/.stamps" @@ -29,29 +30,29 @@ require "ruby-prof" cfg_arch.transitive_implemented_csrs.each do |csr| path = dir_path / "#{csr.name}.adoc" puts " Generating #{path}" - File.write(path, cfg_arch.find_replace_links(erb.result(binding))) + File.write(path, cfg_arch.convert_monospace_to_links(erb.result(binding))) end when "inst" cfg_arch.transitive_implemented_instructions.each do |inst| path = dir_path / "#{inst.name}.adoc" puts " Generating #{path}" # RubyProf.start - File.write(path, cfg_arch.find_replace_links(erb.result(binding))) + File.write(path, cfg_arch.convert_monospace_to_links(erb.result(binding))) # result = RubyProf.stop # RubyProf::FlatPrinter.new(result).print(STDOUT) end when "ext" - cfg_arch.transitive_implemented_extensions.each do |ext_version| - ext = cfg_arch.extension(ext_version.name) + cfg_arch.transitive_implemented_ext_vers.each do |ext_version| + ext = cfg_arch.arch.extension(ext_version.name) path = dir_path / "#{ext.name}.adoc" puts " Generating #{path}" - File.write(path, cfg_arch.find_replace_links(erb.result(binding))) + File.write(path, cfg_arch.convert_monospace_to_links(erb.result(binding))) end when "func" global_symtab = cfg_arch.symtab path = dir_path / "funcs.adoc" puts " Generating #{path}" - File.write(path, cfg_arch.find_replace_links(erb.result(binding))) + File.write(path, cfg_arch.convert_monospace_to_links(erb.result(binding))) else raise "todo" end @@ -88,8 +89,8 @@ require "ruby-prof" lines << " * `#{csr.name}` #{csr.long_name}" end when "ext" - puts "Generating full extension list" - cfg_arch.transitive_implemented_extensions.each do |ext_version| + puts "Generting full extension list" + cfg_arch.transitive_implemented_ext_vers.each do |ext_version| lines << " * `#{ext_version.name}` #{ext_version.ext.long_name}" end when "inst" @@ -106,7 +107,7 @@ require "ruby-prof" raise "Unsupported type" end - File.write t.name, cfg_arch.find_replace_links(lines.join("\n")) + File.write t.name, cfg_arch.convert_monospace_to_links(lines.join("\n")) end end diff --git a/backends/cfg_html_doc/html_gen.rake b/backends/cfg_html_doc/html_gen.rake index 1a2534fa2..908da1ec4 100644 --- a/backends/cfg_html_doc/html_gen.rake +++ b/backends/cfg_html_doc/html_gen.rake @@ -1,40 +1,6 @@ # frozen_string_literal: true -# Utilities for generating an Antora site out of an architecture def -module AntoraUtils - class << self - def resolve_links(path_or_str) - str = - if path_or_str.is_a?(Pathname) - path_or_str.read - else - path_or_str - end - str.gsub(/%%LINK%([^;%]+)\s*;\s*([^;%]+)\s*;\s*([^%]+)%%/) do - type = Regexp.last_match[1] - name = Regexp.last_match[2] - link_text = Regexp.last_match[3] - - case type - when "inst" - "xref:insts:#{name}.adoc##{name}-def[#{link_text.gsub(']', '\]')}]" - when "csr" - "xref:csrs:#{name}.adoc##{name}-def[#{link_text.gsub(']', '\]')}]" - when "csr_field" - csr_name, field_name = name.split('.') - "xref:csrs:#{csr_name}.adoc##{csr_name}-#{field_name}-def[#{link_text.gsub(']', '\]')}]" - when "ext" - "xref:exts:#{name}.adoc##{name}-def[#{link_text.gsub(']', '\]')}]" - when "func" - "xref:funcs:funcs.adoc##{name}-func-def[#{link_text.gsub(']', '\]')}]" - else - raise "Unhandled link type '#{type}' for '#{name}' #{match.captures}" - end - end - end - end -end - +# fill out templates for every csr, inst, ext, and func ["csr", "inst", "ext", "func"].each do |type| rule %r{#{$root}/gen/cfg_html_doc/.*/antora/modules/#{type}s/pages/.*\.adoc} => proc { |tname| config_name = Pathname.new(tname).relative_path_from("#{$root}/gen/cfg_html_doc").to_s.split("/")[0] @@ -86,7 +52,7 @@ rule %r{#{$root}/gen/cfg_html_doc/.*/antora/modules/ROOT/pages/config.adoc} => p cfg_arch = cfg_arch_for(config_name) FileUtils.mkdir_p File.dirname(t.name) - File.write t.name, AntoraUtils.resolve_links(cfg_arch.find_replace_links(erb.result(binding))) + File.write t.name, AntoraUtils.resolve_links(cfg_arch.convert_monospace_to_links(erb.result(binding))) end rule %r{#{$root}/gen/cfg_html_doc/.*/antora/modules/ROOT/pages/landing.adoc} => proc { |tname| @@ -100,7 +66,7 @@ rule %r{#{$root}/gen/cfg_html_doc/.*/antora/modules/ROOT/pages/landing.adoc} => cfg_arch = cfg_arch_for(config_name) FileUtils.mkdir_p File.dirname(t.name) - File.write t.name, AntoraUtils.resolve_links(cfg_arch.find_replace_links(File.read(t.prerequisites[0]))) + File.write t.name, AntoraUtils.resolve_links(cfg_arch.convert_monospace_to_links(File.read(t.prerequisites[0]))) end rule %r{#{$root}/gen/cfg_html_doc/.*/antora/antora.yml} => proc { |tname| diff --git a/backends/cfg_html_doc/templates/config.adoc.erb b/backends/cfg_html_doc/templates/config.adoc.erb index e160d313a..59b777b50 100644 --- a/backends/cfg_html_doc/templates/config.adoc.erb +++ b/backends/cfg_html_doc/templates/config.adoc.erb @@ -4,7 +4,7 @@ |=== | Name | Version -<%- cfg_arch.transitive_implemented_extensions.sort{ |a,b| a.name <=> b.name }.each do |e| -%> +<%- cfg_arch.transitive_implemented_ext_vers.sort{ |a,b| a.name <=> b.name }.each do |e| -%> | `<%= e.name %>` | <%= e.version_spec %> <%- end -%> |=== diff --git a/backends/cfg_html_doc/templates/csr.adoc.erb b/backends/cfg_html_doc/templates/csr.adoc.erb index a2ef0c9ea..1641c1c4d 100644 --- a/backends/cfg_html_doc/templates/csr.adoc.erb +++ b/backends/cfg_html_doc/templates/csr.adoc.erb @@ -73,8 +73,8 @@ This CSR has no fields. However, it must still exist (not cause an `Illegal Inst <%- else -%> <%- csr.implemented_fields(cfg_arch).each do |field| -%> -[[<%=csr.name%>-<%=field.name%>-def]] -=== `<%= field.name %>` +<%= anchor_for_udb_doc_csr_field(csr.name, field.name) %> +=== `<%= field.name %>` Field [.csr-field-info] -- diff --git a/backends/cfg_html_doc/templates/ext.adoc.erb b/backends/cfg_html_doc/templates/ext.adoc.erb index 8111a2ae5..26f367ee4 100644 --- a/backends/cfg_html_doc/templates/ext.adoc.erb +++ b/backends/cfg_html_doc/templates/ext.adoc.erb @@ -7,7 +7,7 @@ Implemented Version:: <%= ext_version.version_str %> == Versions <%- ext.versions.each do |v| -%> -<%- implemented = cfg_arch.transitive_implemented_extensions.include?(v) -%> +<%- implemented = cfg_arch.transitive_implemented_ext_vers.include?(v) -%> <%= v.version_str %>:: Ratification date::: <%= v.ratification_date %> @@ -39,7 +39,7 @@ Implemented Version:: <%= ext_version.version_str %> <%- unless insts.empty? -%> == Instructions -The following instructions are added by this extension in the <%= ext.cfg_arch.name %> configuration: +The following instructions are added by this extension in the <%= cfg_arch.name %> configuration: [cols="1,3"] |=== diff --git a/backends/cfg_html_doc/templates/func.adoc.erb b/backends/cfg_html_doc/templates/func.adoc.erb index 40b87e384..75f04cf53 100644 --- a/backends/cfg_html_doc/templates/func.adoc.erb +++ b/backends/cfg_html_doc/templates/func.adoc.erb @@ -5,7 +5,7 @@ = Functions <%- cfg_arch.implemented_functions.each do |f| -%> -[#<%= f.name %>-func-def] +<%= anchor_for_udb_doc_idl_func(f.name) %> == <%= f.name %><%- if f.builtin? -%> (builtin)<%- end -%> <%= f.description %> diff --git a/backends/cfg_html_doc/templates/inst.adoc.erb b/backends/cfg_html_doc/templates/inst.adoc.erb index d34aa2124..2c3180aed 100644 --- a/backends/cfg_html_doc/templates/inst.adoc.erb +++ b/backends/cfg_html_doc/templates/inst.adoc.erb @@ -1,6 +1,6 @@ :tabs-sync-option: -[[inst:<%=inst.name.gsub('.', '_')%>-def]] +<%= anchor_for_udb_doc_inst(inst.name) %> = <%= inst.name %> *<%= inst.long_name %>* diff --git a/backends/cfg_html_doc/templates/toc.adoc.erb b/backends/cfg_html_doc/templates/toc.adoc.erb index aac4215f0..5aeff181b 100644 --- a/backends/cfg_html_doc/templates/toc.adoc.erb +++ b/backends/cfg_html_doc/templates/toc.adoc.erb @@ -1,18 +1,18 @@ * xref:ROOT:config.adoc[Configuration] .Extensions -<%- cfg_arch.transitive_implemented_extensions.sort { |a, b| a.name <=> b.name }.each do |ext| -%> -* %%LINK%ext;<%= ext.name %>;<%= ext.name %>%% +<%- cfg_arch.transitive_implemented_ext_vers.sort { |a, b| a.name <=> b.name }.each do |ext| -%> +* %%UDB_DOC_LINK%ext;<%= ext.name %>;<%= ext.name %>%% <%- end -%> .Control and Status Registers <%- cfg_arch.transitive_implemented_csrs.sort { |a, b| a.name <=> b.name }.each do |csr| -%> -* %%LINK%csr;<%= csr.name %>;<%= csr.name %>%% +* %%UDB_DOC_LINK%csr;<%= csr.name %>;<%= csr.name %>%% <%- end -%> .Instructions <%- cfg_arch.transitive_implemented_instructions.sort { |a, b| a.name <=> b.name }.each do |inst| -%> -* %%LINK%inst;<%= inst.name %>;<%= inst.name %>%% +* %%UDB_DOC_LINK%inst;<%= inst.name %>;<%= inst.name %>%% <%- end -%> .IDL functions diff --git a/backends/common_templates/adoc/README.adoc b/backends/common_templates/adoc/README.adoc deleted file mode 100644 index a17bf4ffe..000000000 --- a/backends/common_templates/adoc/README.adoc +++ /dev/null @@ -1 +0,0 @@ -This directory contains partial templates (e.g., a CSR description) to be used by document generators. diff --git a/backends/ext_pdf_doc/tasks.rake b/backends/ext_pdf_doc/tasks.rake index bb8a0343c..98342e977 100644 --- a/backends/ext_pdf_doc/tasks.rake +++ b/backends/ext_pdf_doc/tasks.rake @@ -9,44 +9,6 @@ require_relative "#{$lib}/idl/passes/gen_adoc" EXT_PDF_DOC_DIR = Pathname.new "#{$root}/backends/ext_pdf_doc" -# Utilities for generating an Antora site out of an architecture def -module AsciidocUtils - class << self - def resolve_links(path_or_str) - str = - if path_or_str.is_a?(Pathname) - path_or_str.read - else - path_or_str - end - str.gsub(/%%LINK%([^;%]+)\s*;\s*([^;%]+)\s*;\s*([^%]+)%%/) do - type = Regexp.last_match[1] - name = Regexp.last_match[2] - link_text = Regexp.last_match[3] - - case type - when "inst" - "xref:#inst-#{name.gsub('.', '_')}-def[#{link_text.gsub(']', '\]')}]" - when "csr" - "xref:#csr-#{name}-def[#{link_text.gsub(']', '\]')}]" - when "csr_field" - csr_name, field_name = name.split('.') - # "xref:csrs:#{csr_name}.adoc##{csr_name}-#{field_name}-def[#{link_text.gsub(']', '\]')}]" - link_text - when "ext" - # "xref:exts:#{name}.adoc##{name}-def[#{link_text.gsub(']', '\]')}]" - link_text - when "func" - # "xref:funcs:funcs.adoc##{name}-func-def[#{link_text.gsub(']', '\]')}]" - link_text - else - raise "Unhandled link type '#{type}' for '#{name}' #{match.captures}" - end - end - end - end -end - file "#{$root}/ext/docs-resources/themes/riscv-pdf.yml" => "#{$root}/.gitmodules" do |t| system "git submodule update --init ext/docs-resources" end @@ -129,7 +91,7 @@ rule %r{#{$root}/gen/ext_pdf_doc/.*/adoc/.*_extension\.adoc} => proc { |tname| } do |t| config_name = Pathname.new(t.name).relative_path_from("#{$root}/gen/ext_pdf_doc").to_s.split("/")[0] - cfg_arch = cfg_arch_for(config_name) + design = cfg_arch_for(config_name) ext_name = Pathname.new(t.name).basename(".adoc").to_s.split("_")[0..-2].join("_") @@ -137,7 +99,7 @@ rule %r{#{$root}/gen/ext_pdf_doc/.*/adoc/.*_extension\.adoc} => proc { |tname| erb = ERB.new(template_path.read, trim_mode: "-") erb.filename = template_path.to_s - ext = cfg_arch.extension(ext_name) + ext = design.arch.extension(ext_name) version_strs = ENV["VERSION"].split(",") versions = if version_strs.include?("all") @@ -153,7 +115,7 @@ rule %r{#{$root}/gen/ext_pdf_doc/.*/adoc/.*_extension\.adoc} => proc { |tname| max_version = versions.max { |a, b| a.version <=> b.version } FileUtils.mkdir_p File.dirname(t.name) - File.write t.name, AsciidocUtils.resolve_links(cfg_arch.find_replace_links(erb.result(binding))) + File.write t.name, AsciidocUtils.resolve_links(design.convert_monospace_to_links(erb.result(binding))) end namespace :gen do diff --git a/backends/ext_pdf_doc/templates/ext_pdf.adoc.erb b/backends/ext_pdf_doc/templates/ext_pdf.adoc.erb index bd9568a1e..e448d280a 100644 --- a/backends/ext_pdf_doc/templates/ext_pdf.adoc.erb +++ b/backends/ext_pdf_doc/templates/ext_pdf.adoc.erb @@ -224,7 +224,7 @@ version <%= versions.select { |v| v.implications.include?(sub_ext)}.map(&:versio of <%= ext.name %>. <%- end -%> -<%= cfg_arch.extension(sub_ext.name).description %> +<%= design.arch.extension(sub_ext.name).description %> <%- unless sub_ext.requirement_condition.empty? -%> <%= sub_ext.name %> requires: @@ -303,7 +303,7 @@ The following <%= ext.csrs.size %> are added by this extension. <%- ext.csrs.each do |csr| -%> <<< :leveloffset: +2 -<%= partial "adoc/csr.adoc.erb", { csr: csr, cfg_arch: cfg_arch } %> +<%= partial("templates/csr.adoc.erb", { csr: csr, design: design }) %> :leveloffset: -2 <%- end -%> @@ -316,7 +316,7 @@ The following <%= ext.csrs.size %> are added by this extension. <%- ext.instructions.each do |i| -%> :leveloffset: +2 -<%= partial "adoc/inst.adoc.erb", { inst: i, cfg_arch: cfg_arch } %> +<%= partial("templates/inst.adoc.erb", { inst: i, design: design }) %> :leveloffset: -2 <<< @@ -326,8 +326,8 @@ The following <%= ext.csrs.size %> are added by this extension. <<< == IDL Functions -<%- ext.reachable_functions(cfg_arch.symtab).sort { |a,b| a.name <=> b.name }.each do |f| -%> -[#<%= f.name %>-func-def] +<%- ext.reachable_functions(design.symtab).sort { |a,b| a.name <=> b.name }.each do |f| -%> +<%= anchor_for_udb_doc_idl_func(f.name) %> === <%= f.name %><%- if f.builtin? -%> (builtin)<%- end -%> <%= f.description %> diff --git a/backends/manual/tasks.rake b/backends/manual/tasks.rake index d7573071e..024603794 100644 --- a/backends/manual/tasks.rake +++ b/backends/manual/tasks.rake @@ -40,7 +40,7 @@ directory MANUAL_GEN_DIR / "html" file MANUAL_GEN_DIR / "antora" / "antora.yml" => (MANUAL_GEN_DIR / "antora").to_s do |t| File.write t.name, <<~ANTORA name: riscv_manual - version: #{cfg_arch.manual_version?} + version: #{cfg_arch.arch.manual_version?} nav: - modules/nav.adoc title: RISC-V ISA Manual @@ -52,7 +52,7 @@ rule %r{#{MANUAL_GEN_DIR}/.*/.*/antora/modules/chapters/pages/.*\.adoc} do |t| parts = t.name.sub("#{MANUAL_GEN_DIR}/", "").split("/") manual_name = parts[0] version_name = parts[1] - manual = cfg_arch_for("_").manual(parts[0]) + manual = cfg_arch_for("_").arch.manual(parts[0]) manual_version = manual.version(parts[1]) chapter_name = File.basename(t.name, ".adoc") @@ -86,7 +86,7 @@ rule %r{#{MANUAL_GEN_DIR}/.*/.*/antora/antora.yml} => proc { |tname| ] } do |t| parts = t.name.sub("#{MANUAL_GEN_DIR}/", "").split("/") - manual_version = cfg_arch_for("_").manual(parts[0])&.version(parts[1]) + manual_version = cfg_arch_for("_").arch.manual(parts[0])&.version(parts[1]) raise "Can't find any manual version for '#{parts[0]}' '#{parts[1]}'" if manual_version.nil? @@ -123,7 +123,7 @@ rule %r{#{MANUAL_GEN_DIR}/.*/.*/antora/nav.adoc} => proc { |tname| ] } do |t| parts = t.name.sub("#{MANUAL_GEN_DIR}/", "").split("/") - manual_version = cfg_arch_for("_").manual(parts[0])&.version(parts[1]) + manual_version = cfg_arch_for("_").arch.manual(parts[0])&.version(parts[1]) raise "Can't find any manual version for '#{parts[0]}' '#{parts[1]}'" if manual_version.nil? @@ -132,7 +132,7 @@ rule %r{#{MANUAL_GEN_DIR}/.*/.*/antora/nav.adoc} => proc { |tname| raise "There is no navigation file for manual '#{parts[0]}' at '#{nav_template_path}'" end - raise "no cfg_arch" if manual_version.cfg_arch.nil? + raise "no arch" if manual_version.arch.nil? erb = ERB.new(nav_template_path.read, trim_mode: "-") erb.filename = nav_template_path.to_s @@ -166,7 +166,7 @@ rule %r{#{MANUAL_GEN_DIR}/.*/.*/antora/modules/ROOT/pages/index.adoc} => proc { ] } do |t| parts = t.name.sub("#{MANUAL_GEN_DIR}/", "").split("/") - manual_version = cfg_arch_for("_").manual(parts[0])&.version(parts[1]) + manual_version = cfg_arch_for("_").arch.manual(parts[0])&.version(parts[1]) raise "Can't find any manual version for '#{parts[0]}' '#{parts[1]}'" if manual_version.nil? @@ -190,8 +190,8 @@ rule %r{#{MANUAL_GEN_DIR}/.*/.*/antora/modules/insts/pages/.*.adoc} => [ ] do |t| inst_name = File.basename(t.name, ".adoc") - cfg_arch = cfg_arch_for("_") - inst = cfg_arch.instruction(inst_name) + design = cfg_arch_for("_") + inst = design.arch.instruction(inst_name) raise "Can't find instruction '#{inst_name}'" if inst.nil? inst_template_path = $root / "backends" / "manual" / "templates" / "instruction.adoc.erb" @@ -199,31 +199,27 @@ rule %r{#{MANUAL_GEN_DIR}/.*/.*/antora/modules/insts/pages/.*.adoc} => [ erb.filename = inst_template_path.to_s FileUtils.mkdir_p File.dirname(t.name) - File.write t.name, AntoraUtils.resolve_links(cfg_arch.find_replace_links(erb.result(binding))) + File.write t.name, AntoraUtils.resolve_links(design.convert_monospace_to_links(erb.result(binding))) end # rule to create csr appendix page rule %r{#{MANUAL_GEN_DIR}/.*/.*/antora/modules/csrs/pages/.*\.adoc} => [ __FILE__, "gen:arch", - ($root / "backends" / "common_templates" / "adoc" / "csr.adoc.erb").to_s + ($root / "backends" / "templates" / "csr.adoc.erb").to_s ] do |t| csr_name = File.basename(t.name, ".adoc") - cfg_arch = cfg_arch_for("_") - # cfg_arch_32 = cfg_arch_for("_32") - - csr = cfg_arch.csr(csr_name) + design = cfg_arch_for("_") + csr = design.arch.csr(csr_name) raise "Can't find csr '#{csr_name}'" if csr.nil? - # csr_32 = cfg_arch_32.csr(csr_name) - - csr_template_path = $root / "backends" / "common_templates" / "adoc" / "csr.adoc.erb" + csr_template_path = $root / "backends" / "templates" / "csr.adoc.erb" erb = ERB.new(csr_template_path.read, trim_mode: "-") erb.filename = csr_template_path.to_s FileUtils.mkdir_p File.dirname(t.name) - File.write t.name, AntoraUtils.resolve_links(cfg_arch.find_replace_links(erb.result(binding))) + File.write t.name, AntoraUtils.resolve_links(design.convert_monospace_to_links(erb.result(binding))) end # rule to create ext appendix page @@ -233,8 +229,8 @@ rule %r{#{MANUAL_GEN_DIR}/.*/.*/antora/modules/exts/pages/.*.adoc} => [ ] do |t| ext_name = File.basename(t.name, ".adoc") - cfg_arch = cfg_arch_for("_") - ext = cfg_arch.extension(ext_name) + design = cfg_arch_for("_") + ext = design.arch.extension(ext_name) raise "Can't find extension '#{ext_name}'" if ext.nil? ext_template_path = $root / "backends" / "manual" / "templates" / "ext.adoc.erb" @@ -242,7 +238,7 @@ rule %r{#{MANUAL_GEN_DIR}/.*/.*/antora/modules/exts/pages/.*.adoc} => [ erb.filename = ext_template_path.to_s FileUtils.mkdir_p File.dirname(t.name) - File.write t.name, AntoraUtils.resolve_links(cfg_arch.find_replace_links(erb.result(binding))) + File.write t.name, AntoraUtils.resolve_links(design.convert_monospace_to_links(erb.result(binding))) end # rule to create IDL function appendix page @@ -250,31 +246,31 @@ rule %r{#{MANUAL_GEN_DIR}/.*/.*/antora/modules/funcs/pages/funcs.adoc} => [ __FILE__, ($root / "backends" / "manual" / "templates" / "func.adoc.erb").to_s ] do |t| - cfg_arch = cfg_arch_for("_") + design = cfg_arch_for("_") funcs_template_path = $root / "backends" / "manual" / "templates" / "func.adoc.erb" erb = ERB.new(funcs_template_path.read, trim_mode: "-") erb.filename = funcs_template_path.to_s FileUtils.mkdir_p File.dirname(t.name) - File.write t.name, AntoraUtils.resolve_links(cfg_arch.find_replace_links(erb.result(binding))) + File.write t.name, AntoraUtils.resolve_links(design.convert_monospace_to_links(erb.result(binding))) end -# rule to create IDL function appendix page +# rule to create parameter list appendix page rule %r{#{MANUAL_GEN_DIR}/.*/.*/antora/modules/params/pages/param_list.adoc} => [ __FILE__, ($root / "backends" / "manual" / "templates" / "param_list.adoc.erb").to_s ] do |t| - cfg_arch = cfg_arch_for("_") + design = cfg_arch_for("_") parts = t.name.sub("#{MANUAL_GEN_DIR}/", "").split("/") - manual_version = cfg_arch.manual(parts[0])&.version(parts[1]) + manual_version = design.arch.manual(parts[0])&.version(parts[1]) param_list_template_path = $root / "backends" / "manual" / "templates" / "param_list.adoc.erb" erb = ERB.new(param_list_template_path.read, trim_mode: "-") erb.filename = param_list_template_path.to_s FileUtils.mkdir_p File.dirname(t.name) - File.write t.name, AntoraUtils.resolve_links(cfg_arch.find_replace_links(erb.result(binding))) + File.write t.name, AntoraUtils.resolve_links(design.convert_monospace_to_links(erb.result(binding))) end rule %r{#{MANUAL_GEN_DIR}/.*/top/.*/antora/landing/antora.yml} => [ @@ -283,8 +279,8 @@ rule %r{#{MANUAL_GEN_DIR}/.*/top/.*/antora/landing/antora.yml} => [ parts = t.name.sub("#{MANUAL_GEN_DIR}/", "").split("/") manual_name = parts[0] - cfg_arch = cfg_arch_for("_") - manual = cfg_arch.manual(manual_name) + design = cfg_arch_for("_") + manual = design.arch.manual(manual_name) raise "Can't find any manual version for '#{manual_name}'" if manual.nil? FileUtils.mkdir_p File.basename(t.name) @@ -316,7 +312,7 @@ rule %r{#{MANUAL_GEN_DIR}/.*/top/.*/antora/landing/modules/ROOT/pages/index.adoc erb = ERB.new(landing_template_path.read, trim_mode: "-") erb.filename = landing_template_path.to_s - manual = cfg_arch_for("_").manual(manual_name) + manual = cfg_arch_for("_").arch.manual(manual_name) FileUtils.mkdir_p File.dirname(t.name) File.write t.name, erb.result(binding) @@ -344,7 +340,7 @@ rule %r{#{MANUAL_GEN_DIR}/.*/top/.*/antora/playbook/playbook.yml} => proc { |tna erb = ERB.new(playbook_template_path.read, trim_mode: "-") erb.filename = playbook_template_path.to_s - manual = cfg_arch_for("_").manual(manual_name) + manual = cfg_arch_for("_").arch.manual(manual_name) FileUtils.mkdir_p File.dirname(t.name) File.write t.name, erb.result(binding) @@ -400,15 +396,15 @@ namespace :gen do raise ArgumentError, "Missing required environment variable VERSIONS\n\n#{html_manual_desc}" if ENV["VERSIONS"].nil? versions, output_hash = versions_from_env(ENV["MANUAL_NAME"]) - cfg_arch = cfg_arch_for("_") + design = cfg_arch_for("_") - manual = cfg_arch.manual(ENV["MANUAL_NAME"]) + manual = design.arch.manual(ENV["MANUAL_NAME"]) raise "No manual named '#{ENV['MANUAL_NAME']}" if manual.nil? # check out the correct version of riscv-isa-manual, if needed versions.each do |version| - version_obj = cfg_arch.manual_version(version) + version_obj = design.arch.manual_version(version) manual.repo_path = MANUAL_GEN_DIR / ENV["MANUAL_NAME"] / version / "riscv-isa-manual" @@ -436,7 +432,7 @@ namespace :gen do version_obj.instructions.each do |inst| Rake::Task[antora_path / "modules" / "insts" / "pages" / "#{inst.name}.adoc"].invoke end - version_obj.extensions.each do |ext| + version_obj.ext_vers.each do |ext| Rake::Task[antora_path / "modules" / "exts" / "pages" / "#{ext.name}.adoc"].invoke end Rake::Task[antora_path / "modules" / "params" / "pages" / "param_list.adoc"].invoke @@ -473,8 +469,8 @@ namespace :serve do port = ENV.key?("PORT") ? ENV["PORT"] : 8000 - cfg_arch = cfg_arch_for("_") - manual = cfg_arch.manuals.find { |m| m.name == ENV["MANUAL_NAME"] } + design = cfg_arch_for("_") + manual = design.arch.manuals.find { |m| m.name == ENV["MANUAL_NAME"] } raise "No manual '#{ENV['MANUAL_NAME']}'" if manual.nil? _, output_hash = versions_from_env(manual) diff --git a/backends/manual/templates/csr.adoc.erb b/backends/manual/templates/csr.adoc.erb index e4e241cf5..ad673adf6 100644 --- a/backends/manual/templates/csr.adoc.erb +++ b/backends/manual/templates/csr.adoc.erb @@ -1,11 +1,11 @@ :tabs-sync-option: -[#csrs-<%= csr.name.gsub('.', '_') %>,reftext=<%= csr.name %>] +<%= anchor_for_udb_doc_csr(csr.name) %> = <%= csr.name %> *<%= csr.long_name %>* -<%= cfg_arch.render_erb(csr.description, "#{csr.name}.description") %> +<%= design.render_erb(csr.description, "#{csr.name}.description") %> == Attributes [%autowidth] @@ -15,7 +15,7 @@ h| CSR Address | <%= "0x#{csr.address.to_s(16)}" %> <%- if csr.priv_mode == 'VS' -%> h| Virtual CSR Address | <%= "0x#{csr.virtual_address.to_s(16)}" %> <%- end -%> -<%- if csr.dynamic_length?(cfg_arch) || csr.data["length"] == "MXLEN" -%> +<%- if csr.dynamic_length?(design) || csr.data["length"] == "MXLEN" -%> h| Length a| @@ -26,22 +26,22 @@ a| [when,"<%= csr.length_cond64 %>"] -- -<%= csr.length_pretty(cfg_arch, 64) %> +<%= csr.length_pretty(design, 64) %> -- <%- else -%> -h| Length | <%= csr.length_pretty(cfg_arch) %> +h| Length | <%= csr.length_pretty(design) %> <%- end -%> h| Privilege Mode | <%= csr.priv_mode %> |=== == Format -<%- unless csr.dynamic_length?(cfg_arch) || csr.fields.any? { |f| f.dynamic_location?(cfg_arch) } || csr.data["length"] == "MXLEN" -%> +<%- unless csr.dynamic_length?(design) || csr.fields.any? { |f| f.dynamic_location?(design) } || csr.data["length"] == "MXLEN" -%> <%# CSR has a known static length, so there is only one format to display -%> .<%= csr.name %> format [wavedrom, ,svg,subs='attributes',width="100%"] .... -<%= JSON.dump csr.wavedrom_desc(cfg_arch, 64) %> +<%= JSON.dump csr.wavedrom_desc(design, 64) %> .... <%- else -%> <%# CSR has a dynamic length, or a field has a dynamic location, @@ -52,7 +52,7 @@ This CSR format changes dynamically. -- [wavedrom, ,svg,subs='attributes',width="100%"] .... -<%= JSON.dump csr_32.wavedrom_desc(cfg_arch_32, 32) %> +<%= JSON.dump csr_32.wavedrom_desc(design, 32) %> .... -- @@ -60,7 +60,7 @@ This CSR format changes dynamically. -- [wavedrom, ,svg,subs='attributes',width="100%"] .... -<%= JSON.dump csr.wavedrom_desc(cfg_arch, 64) %> +<%= JSON.dump csr.wavedrom_desc(design, 64) %> .... -- <%- end -%> @@ -73,33 +73,33 @@ This CSR format changes dynamically. @ Name @ Location @ Type @ Reset Value <%- csr.fields.each do |field| -%> -@ xref:<%=csr.name%>-<%=field.name%>-def[`<%= field.name %>`] +@ <%= link_to_udb_doc_csr_field(csr.name, field.name) %> a@ -<%- if field.dynamic_location?(cfg_arch) -%> +<%- if field.dynamic_location?(design) -%> [when,"<%= field.location_cond32 %>"] -- -<%= field.location_pretty(cfg_arch, 32) %> +<%= field.location_pretty(design, 32) %> -- [when,"<%= field.location_cond64 %>"] -- -<%= field.location_pretty(cfg_arch, 64) %> +<%= field.location_pretty(design, 64) %> -- <%- else -%> -<%= field.location_pretty(cfg_arch) %> +<%= field.location_pretty(design) %> <%- end -%> a@ -- -<%= field.type_pretty(cfg_arch.symtab) %> +<%= field.type_pretty(design.symtab) %> -- a@ -- -<%= field.reset_value_pretty(cfg_arch) %> +<%= field.reset_value_pretty(design) %> -- <%- end -%> @@ -112,8 +112,8 @@ This CSR has no fields. However, it must still exist (not cause an `Illegal Inst <%- else -%> <%- csr.fields.each do |field| -%> -[[<%=csr.name%>-<%=field.name%>-def]] -=== `<%= field.name %>` +<%= anchor_for_udb_doc_csr_field(csr.name, field.name) %> +=== `<%= field.name %>` Field <%- if !field.defined_in_all_bases? -%> IMPORTANT: <%= field.name %> is only defined in <%= field.base32_only? ? "RV32" : "RV64" %> (`<%= field.base32_only? ? field.location_cond32 : field.location_cond64 %>`) @@ -121,16 +121,16 @@ IMPORTANT: <%= field.name %> is only defined in <%= field.base32_only? ? "RV32" **** Location:: -<%= field.location_pretty(cfg_arch) %> +<%= field.location_pretty(design) %> Description:: -<%= cfg_arch.render_erb(field.description, "#{csr.name}.#{field.name}.description").gsub("\n", " +\n") %> +<%= design.render_erb(field.description, "#{csr.name}.#{field.name}.description").gsub("\n", " +\n") %> Type:: -<%= field.type_pretty(cfg_arch.symtab) %> +<%= field.type_pretty(design.symtab) %> Reset value:: -<%= field.reset_value_pretty(cfg_arch) %> +<%= field.reset_value_pretty(design) %> **** @@ -164,6 +164,6 @@ This CSR may return a value that is different from what is stored in hardware. [source,idl,subs="specialchars,macros"] ---- -<%= csr.sw_read_ast(cfg_arch.symtab).gen_adoc %> +<%= csr.sw_read_ast(design.symtab).gen_adoc %> ---- <%- end -%> diff --git a/backends/manual/templates/ext.adoc.erb b/backends/manual/templates/ext.adoc.erb index 71a46f857..e43ee06ea 100644 --- a/backends/manual/templates/ext.adoc.erb +++ b/backends/manual/templates/ext.adoc.erb @@ -1,4 +1,4 @@ -[ext:$<%= ext.name %>-def] +<%= anchor_for_udb_doc_ext(ext.name) %> = <%= ext.name %> Extension <%= ext.long_name %> @@ -36,7 +36,7 @@ <%= ext.description %> -<%- insts = cfg_arch.instructions.select { |i| ext.versions.any? { |v| i.defined_by?(v) } } -%> +<%- insts = design.arch.instructions.select { |i| ext.versions.any? { |v| i.defined_by?(v) } } -%> <%- unless insts.empty? -%> == Instructions @@ -53,9 +53,10 @@ The following instructions are defined by this extension: <%- unless ext.params.empty? -%> == Parameters -This extension has the following implementation options: +This extension has the following implementation options (AKA parameters): <%- ext.params.sort_by { |p| p.name }.each do |param| -%> +<%= anchor_for_udb_doc_ext_param(ext.name, param.name) %> <%= param.name %>:: + -- diff --git a/backends/manual/templates/func.adoc.erb b/backends/manual/templates/func.adoc.erb index a448d555c..1fac9a513 100644 --- a/backends/manual/templates/func.adoc.erb +++ b/backends/manual/templates/func.adoc.erb @@ -4,8 +4,8 @@ = Functions -<%- cfg_arch.functions.each do |f| -%> -[#<%= f.name %>-func-def] +<%- design.functions.each do |f| -%> +<%= anchor_for_udb_doc_idl_func(f.name) %> == <%= f.name %><%- if f.builtin? -%> (builtin)<%- end -%> <%= f.description %> diff --git a/backends/manual/templates/instruction.adoc.erb b/backends/manual/templates/instruction.adoc.erb index 1c67a226c..cb09ada2e 100644 --- a/backends/manual/templates/instruction.adoc.erb +++ b/backends/manual/templates/instruction.adoc.erb @@ -1,6 +1,6 @@ :tabs-sync-option: -[[inst:<%=inst.name.gsub('.', '_')%>-def]] +<%= anchor_for_udb_doc_inst(inst.name) %> = <%= inst.name %> *<%= inst.long_name %>* @@ -11,7 +11,7 @@ This instruction is defined by: This instruction is included in the following profiles: -<%- cfg_arch.profiles.each do |profile| -%> +<%- design.arch.profiles.each do |profile| -%> <%- in_profile_mandatory = profile.mandatory_ext_reqs.any? do |ext_req| ext_versions = ext_req.satisfying_versions @@ -128,7 +128,7 @@ IDL:: + [source,idl,subs="specialchars,macros"] ---- -<%= inst.operation_ast(inst.cfg_arch.symtab).gen_adoc %> +<%= inst.operation_ast(design.symtab).gen_adoc %> ---- <%- end -%> @@ -142,7 +142,7 @@ Sail:: <%- end -%> ==== -<% exception_list = inst.reachable_exceptions_str(inst.cfg_arch.symtab, 64) -%> +<% exception_list = inst.reachable_exceptions_str(design.symtab, 64) -%> <%- unless exception_list.empty? -%> == Exceptions diff --git a/backends/manual/templates/isa_nav.adoc.erb b/backends/manual/templates/isa_nav.adoc.erb index 93ebd8857..c75400e2c 100644 --- a/backends/manual/templates/isa_nav.adoc.erb +++ b/backends/manual/templates/isa_nav.adoc.erb @@ -8,17 +8,17 @@ * Alphabetical list of instructions <%- manual_version.instructions.sort { |a, b| a.name <=> b.name }.each do |inst| -%> -** xref:insts:<%= inst.name %>.adoc[<%= inst.name %>] +** <%= link_to_udb_doc_inst(inst.name) %> <%- end -%> * Alphabetical list of CSRs <%- manual_version.csrs.sort { |a, b| a.name <=> b.name }.each do |csr| -%> -** xref:csrs:<%= csr.name %>.adoc[<%= csr.name %>] +** <%= link_to_udb_doc_csr(csr.name) %> <%- end -%> * Alphabetical list of extensions -<%- manual_version.extensions.sort { |a, b| a.name <=> b.name }.each do |ext| -%> -** xref:exts:<%= ext.name %>.adoc[<%= ext.name %>] +<%- manual_version.ext_vers.sort { |a, b| a.name <=> b.name }.each do |ext_ver| -%> +** <%= link_to_udb_doc_ext(ext_ver.name) %> <%- end -%> * xref:params:param_list.adoc[Alphabetical list of parameters] diff --git a/backends/manual/templates/param_list.adoc.erb b/backends/manual/templates/param_list.adoc.erb index dd273b85b..0cad6885c 100644 --- a/backends/manual/templates/param_list.adoc.erb +++ b/backends/manual/templates/param_list.adoc.erb @@ -1,7 +1,7 @@ = Architectural Parameters <%- - params = manual_version.extensions.map{ |e| e.params }.flatten.uniq(&:name).sort_by!(&:name) + params = manual_version.ext_vers.map{ |e| e.params }.flatten.uniq(&:name).sort_by!(&:name) -%> The following <%= params.size %> parameters are defined in this manual: @@ -12,7 +12,7 @@ The following <%= params.size %> parameters are defined in this manual: <%- params.each do |param| -%> | <%= param.name %> | <%= param.schema.to_pretty_s %> -| <%= param.exts.map { |ext| "`#{ext.name}`"}.join(", ") %> +| <%= param.exts.map { |ext| link_to_udb_doc_ext_param(ext.name, param.name, ext.name) }.join(", ") %> a| <%= param.desc %> <%- end -%> |=== diff --git a/backends/portfolio/README.adoc b/backends/portfolio/README.adoc new file mode 100644 index 000000000..1f3ffee0f --- /dev/null +++ b/backends/portfolio/README.adoc @@ -0,0 +1,2 @@ +This portfolio backend isn't a real backend. +Instead, it contains common Rake code and ERB templates shared by multiple portfolio-based backends. diff --git a/backends/portfolio/tasks.rake b/backends/portfolio/tasks.rake new file mode 100644 index 000000000..05a7bdecd --- /dev/null +++ b/backends/portfolio/tasks.rake @@ -0,0 +1,113 @@ +# frozen_string_literal: true +# +# Contains common methods called from portfolio-based tasks.rake files. + +require "pathname" +require "asciidoctor-pdf" +require "asciidoctor-diagram" +require_relative "#{$lib}/idl/passes/gen_adoc" + +# @return [Architecture] +def pf_create_arch + # Ensure that unconfigured resolved architecture called "_" exists. + Rake::Task["#{$root}/.stamps/resolve-_.stamp"].invoke + + # Create architecture object so we can have it create the ProcCertModel. + # Use the unconfigured resolved architecture called "_". + Architecture.new("RISC-V Architecture", $root / "gen" / "resolved_arch" / "_") +end + +# @param erb_template_pname [String] Path to ERB template file +# @param erb_binding [Binding] Path to ERB template file +# @param target_pname [String] Full name of adoc file being generated +# @param portfolio_design [PortfolioDesign] Portfolio design being generated +def pf_create_adoc(erb_template_pname, erb_binding, target_pname, portfolio_design) + template_path = Pathname.new(erb_template_pname) + erb = ERB.new(File.read(template_path), trim_mode: "-") + erb.filename = template_path.to_s + + FileUtils.mkdir_p File.dirname(target_pname) + + # Convert ERB to final ASCIIDOC. Note that this code is broken up into separate function calls + # each with a variable name to aid in running a command-line debugger on this code. + puts "UPDATE: Converting ERB template to adoc for #{portfolio_design.name}" + erb_result = erb.result(erb_binding) + erb_result_monospace_converted_to_links = portfolio_design.convert_monospace_to_links(erb_result) + erb_result_with_links_resolved = AsciidocUtils.resolve_links(erb_result_monospace_converted_to_links) + + File.write(target_pname, erb_result_with_links_resolved) + puts "UPDATE: Generated adoc in #{target_pname}" +end + +# @param adoc_file [String] Full name of source adoc file +# @param target_pname [String] Full name of PDF file being generated +def pf_adoc2pdf(adoc_file, target_pname) + FileUtils.mkdir_p File.dirname(target_pname) + + puts "UPDATE: Generating PDF in #{target_pname}" + cmd = [ + "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 #{target_pname}", + adoc_file + ].join(" ") + + puts "UPDATE: bundle exec #{cmd}" + + # Write out command used to convert adoc to PDF to allow running this + # manually during development. + run_pname = File.dirname(adoc_file) + "/adoc2pdf.sh" + sh "rm -f #{run_pname}" + sh "echo '#!/bin/bash' >#{run_pname}" + sh "echo >>#{run_pname}" + sh "echo bundle exec #{cmd} >>#{run_pname}" + sh "chmod +x #{run_pname}" + + # Now run the actual command. + sh cmd + + puts "UPDATE: Generated PDF in #{target_pname}" +end + +# @param adoc_file [String] Full name of source adoc file +# @param target_pname [String] Full name of HTML file being generated +def pf_adoc2html(adoc_file, target_pname) + FileUtils.mkdir_p File.dirname(target_pname) + + puts "UPDATE: Generating HTML in #{target_pname}" + cmd = [ + "asciidoctor", + "-w", + "-v", + "-a toc", + "-a imagesdir=#{$root}/ext/docs-resources/images", + "-r asciidoctor-diagram", + "-r #{$root}/backends/ext_pdf_doc/idl_lexer", + "-o #{target_pname}", + adoc_file + ].join(" ") + + puts "UPDATE: bundle exec #{cmd}" + + # Write out command used to convert adoc to HTML to allow running this + # manually during development. + run_pname = File.dirname(adoc_file) + "/adoc2html.sh" + sh "rm -f #{run_pname}" + sh "echo '#!/bin/bash' >#{run_pname}" + sh "echo >>#{run_pname}" + sh "echo bundle exec #{cmd} >>#{run_pname}" + sh "chmod +x #{run_pname}" + + # Now run the actual command. + sh cmd + + puts "UPDATE: Generated HTML in #{target_pname}" +end diff --git a/backends/portfolio/templates/README.adoc b/backends/portfolio/templates/README.adoc new file mode 100644 index 000000000..237833699 --- /dev/null +++ b/backends/portfolio/templates/README.adoc @@ -0,0 +1 @@ +This directory contains partial ERB templates shared by multiple portfolio-based backend. diff --git a/backends/portfolio/templates/beginning.adoc.erb b/backends/portfolio/templates/beginning.adoc.erb new file mode 100644 index 000000000..460319544 --- /dev/null +++ b/backends/portfolio/templates/beginning.adoc.erb @@ -0,0 +1,53 @@ +[[header]] +:description: <%= design.name %> <%= design.portfolio_design_type %> +// :revnumber: TODO +:revmark: "TODO: revmark" +:company: RISC-V +:url-riscv: https://riscv.org +:preface-title: Licensing and Acknowledgements +:colophon: +:appendix-caption: Appendix +:title-logo-image: image:risc-v_logo.png["RISC-V International Logo",pdfwidth=3.25in,align=center] +:back-cover-image: image:riscv-horizontal-color.svg[opacity=25%] + +// Settings +:experimental: +:reproducible: +:wavedrom: <%= $root %>/node_modules/.bin/wavedrom-cli +// TODO: needs to be changed +:imagesoutdir: images +:icons: font +:lang: en +:example-caption: Example +:listing-caption: Listing +:table-caption: Table +:figure-caption: Figure +:xrefstyle: short +:chapter-refsig: Chapter +:section-refsig: Section +:appendix-refsig: Appendix +:sectnums: +:toc: left +:toclevels: 5 +// Determined that uncommenting this causes cross-references to IDL functions +// from instruction IDL code to not link. The IDL code uses this +// block tag to get "source" formatting: +// [source,idl,subs="specialchars,macros"] +// +// :source-highlighter: pygments +// ifdef::backend-pdf[] +// :source-highlighter: rouge +// endif::[] +:data-uri: +:hide-uri-scheme: +:stem: +:footnote: +:stem: latexmath +:footnote: +:le: ≤ +:ge: ≥ +:ne: ≠ +:approx: ≈ +:inf: ∞ +:csrname: envcfg +:imagesdir: images diff --git a/backends/portfolio/templates/coverage_points.adoc.erb b/backends/portfolio/templates/coverage_points.adoc.erb new file mode 100644 index 000000000..703354952 --- /dev/null +++ b/backends/portfolio/templates/coverage_points.adoc.erb @@ -0,0 +1,23 @@ +<% unless db_obj.cert_coverage_points.empty? -%> +<% nice_name = db_obj.is_a?(CsrField) ? "#{db_obj.parent.name}.#{db_obj.name}" : db_obj.name -%> +<% if defined?(use_description_list) && use_description_list -%> +Coverage Points for `<%= nice_name %>`: :: +<% else -%> +==== Coverage Points for `<%= nice_name %>` +<% end -%> + +[%autowidth] +|=== +| ID | Name | Description | Documentation Links + +<% db_obj.cert_coverage_points.each do |cp| -%> +| <%= defined?(org) ? anchor_for_udb_doc_cov_pt(org, cp.id) : "" %><%= cp.id %> +| <%= cp.name %> +| <%= cp.description %> +a| <% cp.doc_links.each do |link| -%> +* <%= link.to_adoc %> +<% end # each link -%> +<% end # each coverage point -%> +|=== + +<% end # unless no coverage points -%> diff --git a/backends/portfolio/templates/csr_appendix.adoc.erb b/backends/portfolio/templates/csr_appendix.adoc.erb new file mode 100644 index 000000000..b61a41f82 --- /dev/null +++ b/backends/portfolio/templates/csr_appendix.adoc.erb @@ -0,0 +1,189 @@ +<<< +[appendix] +== CSR Details + +<% portfolio_design.in_scope_csrs.sort_by(&:name).each do |csr| -%> +<<< +<%= anchor_for_udb_doc_csr(csr.name) %> +=== <%= csr.name %> + +*<%= csr.long_name %>* + +<% unless csr.base.nil? -%> +[NOTE] +-- +`<%= csr.name %>` is only defined in RV<%= csr.base %>. +-- +<% end -%> + +<%= csr.description %> + +==== Attributes +[%autowidth] +|=== +h| CSR Address | <%= "0x#{csr.address.to_s(16)}" %> +<% if csr.priv_mode == 'VS' -%> +h| Virtual CSR Address | <%= "0x#{csr.virtual_address.to_s(16)}" %> +<% end -%> +h| Defining extension a| <%= csr.defined_by_condition.to_asciidoc %> +<% if csr.dynamic_length?(design) -%> +h| Length | <%= csr.length_pretty(design) %> +<% else -%> +h| Length | <%= csr.length_pretty(design) %> +<% end -%> +h| Privilege Mode | <%= csr.priv_mode %> +|=== + + +==== Format +<% unless csr.dynamic_length?(design) || csr.implemented_fields(design).any? { |f| f.dynamic_location?(design) } -%> +<%# CSR has a known static length, so there is only one format to display -%> +.<%= csr.name %> format +[wavedrom, ,svg,subs='attributes',width="100%"] +.... +<%= JSON.dump csr.wavedrom_desc(design, csr.base.nil? ? 32 : csr.base, optional_type: 2) %> +.... +<% else -%> +<%# CSR has a dynamic length, or a field has a dynamic location, + so there is more than one format to display -%> +This CSR format changes dynamically with XLEN. + +.<%= csr.name %> Format when <%= csr.length_cond32 %> +[wavedrom, ,svg,subs='attributes',width="100%"] +.... +<%= JSON.dump csr.wavedrom_desc(design, 32, optional_type: 2) %> +.... + +.<%= csr.name %> Format when <%= csr.length_cond64 %> +[wavedrom, ,svg,subs='attributes',width="100%"] +.... +<%= JSON.dump csr.wavedrom_desc(design, 64, optional_type: 2) %> +.... + + +<% end # unless dynamic length -%> + +==== Field Summary + +// use @ as a separator since IDL code can contain | +[%autowidth,separator=@,float="center",align="center",cols="^,<,<,<",options="header",role="stretch"] +|=== +@ Name @ Location @ Type @ Reset Value + +<% csr.implemented_fields(design).each do |field| -%> +@ <%= link_to_udb_doc_csr_field(csr.name, field.name) %> +a@ +<% if field.dynamic_location?(design) -%> + +[when,"<%= field.location_cond32 %>"] +-- +<%= field.location_pretty(design, 32) %> +-- + +[when,"<%= field.location_cond64 %>"] +-- +<%= field.location_pretty(design, 64) %> +-- + +<% else -%> +<%= field.location_pretty(design) %> +<% end -%> +a@ + +-- +<%= field.type_pretty(design.symtab) %> +-- + +a@ + +-- +<%= field.reset_value_pretty(design) %> +-- + +<% end -%> +|=== + +==== Fields + +<% if csr.implemented_fields(design).empty? -%> +This CSR has no fields. However, it must still exist (not cause an `Illegal Instruction` trap) and always return zero on a read. +<% else -%> + +<% csr.implemented_fields(design).each do |field| -%> +<%= anchor_for_udb_doc_csr_field(csr.name, field.name) %> +===== `<%= csr.name %>.<%= field.name %>` Field + +<% if !field.defined_in_all_bases? -%> +IMPORTANT: <%= field.name %> is only defined in <%= field.base32_only? ? "RV32" : "RV64" %> (`<%= field.base32_only? ? field.location_cond32 : field.location_cond64 %>`) +<% end -%> + +// These four asterisks are called a "delimited sidebar block" +// (see https://docs.asciidoctor.org/asciidoc/latest/blocks/sidebars/) +// and cause the text until the next four asterisks to have a thin border around it. +// You can also use the [sidebar] attribute on a block. +// +// One limitation of sidebars is the normal section heading syntax +// (e.g., == for a level 1 heading) won't work. Instead, you have to +// do what's described in https://github.com/asciidoctor/asciidoctor/issues/1709 +// but that would be a level 5 heading in this CSR field case which is pretty ugly. + +**** +Location: :: +<%= field.location_pretty(design) %> + +Description: :: +<%= field.description.gsub("\n", " +\n") %> + +Type: :: +<%= field.type_pretty(design.symtab) %> + +Reset value: :: +<%= field.reset_value_pretty(design) %> + +<% if defined?(gen_ctp_content) && gen_ctp_content -%> +<%= portfolio_design.include_erb("coverage_points.adoc.erb", { "db_obj" => field, "org" => "appendix", "use_description_list" => true }) %> +<%= portfolio_design.include_erb("test_procedures.adoc.erb", { "db_obj" => field, "org" => "appendix", "use_description_list" => true }) %> +<% end # if gen_ctp_content -%> + +**** + +<% end # Each field -%> +<% end # if no fields -%> + +<% if csr.implemented_fields(design).map(&:has_custom_sw_write?).any? -%> +==== Software write + +This CSR may store a value that is different from what software attempts to write. + +When a software write occurs (_e.g._, through `csrrw`), the following determines the +written value: + +[idl] +---- +<% csr.implemented_fields(design).each do |field| -%> +<% if field.has_custom_sw_write? -%> +<%= field.name %> = <%= field["sw_write(csr_value)"] %> +<% else -%> +<%= field.name %> = csr_value.<%= field.name %> +<% end -%> +<% end -%> +---- +<% end -%> + +<% if csr.has_custom_sw_read? -%> +==== Software read + +This CSR may return a value that is different from what is stored in hardware. + +[source,idl,subs="specialchars,macros"] +---- +<%= csr.sw_read_ast(design.symtab).gen_adoc %> +---- +<% end -%> + +<% if defined?(gen_ctp_content) && gen_ctp_content -%> +<%= portfolio_design.include_erb("coverage_points.adoc.erb", { "db_obj" => csr, "org" => "appendix" }) %> +<%= portfolio_design.include_erb("test_procedures.adoc.erb", { "db_obj" => csr, "org" => "appendix" }) %> +<% end # if gen_ctp_content -%> + +<% end # do in_scope_csrs -%> diff --git a/backends/portfolio/templates/ext_appendix.adoc.erb b/backends/portfolio/templates/ext_appendix.adoc.erb new file mode 100644 index 000000000..721fea5f8 --- /dev/null +++ b/backends/portfolio/templates/ext_appendix.adoc.erb @@ -0,0 +1,130 @@ +<<< +[appendix] +== Extension Details +<% portfolio_design.in_scope_ext_reqs.each do |ext_req| -%> +<% ext = arch.extension(ext_req.name) -%> + +<%= anchor_for_udb_doc_ext(ext_req.name) %> +=== Extension <%= ext_req.name %> + +*Long Name*: <%= ext.long_name %> + +*Version Requirement*: <%= ext_req.requirement_specs.map(&:to_s).join(", ") %> + +<% if portfolios.size > 1 -%> +.<%= ext.name %> Extension Presence +|=== +| <%= portfolio_kind %> | v<%= ext.versions.map { |ext_ver| ext_ver.canonical_version.to_s }.join(" | v") %> + +<% portfolios.each do |portfolio| -%> +| <%= portfolio.name %> | <%= portfolio.version_greatest_presence(ext.name, ext.versions).join(" | ") -%> +<% end -%> + +|=== +<% end # portfolios.size > 1 -%> + +<% ext.versions.each do |v| -%> +<%= v.canonical_version %>:: + State::: + <%= v.state %> + <% if v.state == "ratified" && !v.ratification_date.nil? -%> + Ratification date::: + <%= v.ratification_date %> + <% end # if %> + <% unless v.changes.empty? -%> + Changes::: + + <% v.changes.each do |c| -%> + * <%= c %> + <% end -%> + + <% end -%> + <% unless v.url.nil? -%> + Ratification document::: + <%= v.url %> + <% end -%> + <% unless v.implications.empty? -%> + Implies::: + <% v.implications.each do |i| -%> + * `<%= i.name %>` version <%= i.version_spec %> + <% end -%> + <% end -%> +<% end -%> + +==== Synopsis + +:leveloffset: +3 + +<%= ext.description %> + +:leveloffset: -3 + +<% unless ext_req.note.nil? -%> +[NOTE] +-- +<%= ext_req.note %> +-- +<% end -%> + +// TODO: GitHub issue 92: Use version specified by each profile. +<% insts = portfolio_design.in_scope_instructions.select { |i| i.defined_by?(ext.min_version) } -%> +<% unless insts.empty? -%> +==== Instructions + +The following instructions are added by this extension: + +[cols="1,3"] +|=== +<% insts.sort.each do |inst| -%> +| <%= link_to_udb_doc_inst(inst.name) %> +| *<%= inst.long_name %>* +<% end -%> +|=== +<% end -%> + +<% unless ext.params.empty? -%> +<% if portfolio_design.in_scope_params(ext_req).empty? && portfolio_design.out_of_scope_params(ext_req.name).empty? -%> +==== Parameters + +This extension has the following parameters (AKA implementation options): + +<% ext.params.sort_by { |p| p.name }.each do |param| -%> +<%= param.name %>:: ++ +-- +<%= param.desc %> +-- +<% end # do param -%> + +<% end # if in_scope & out_of_scope empty -%> +<% end # unless table -%> + +<% unless portfolio_design.in_scope_params(ext_req).empty? -%> +==== IN-SCOPE Parameters + +<% portfolio_design.in_scope_params(ext_req).each do |param| -%> +<%= anchor_for_udb_doc_ext_param(ext_req.name, param.name) %> +<%= param.name %> ⇒ <%= param.param.schema_type %>:: ++ +-- +<%= param.param.desc %> +-- +<% end # do param -%> +<% end # unless table -%> + +<% unless portfolio_design.out_of_scope_params(ext_req.name).empty? -%> +==== OUT-OF-SCOPE Parameters + +<% portfolio_design.out_of_scope_params(ext_req.name).each do |param| -%> +<%= anchor_for_udb_doc_ext_param(ext_req.name, param.name) %> +<%= param.name %> ⇒ <%= param.schema_type %>:: ++ +-- +<%= param.desc %> +-- +<% end # do param -%> +<% end # unless table -%> + +<% if defined?(gen_ctp_content) && gen_ctp_content -%> +<%= portfolio_design.include_erb("coverage_points.adoc.erb", { "db_obj" => ext, "org" => "appendix"}) %> +<%= portfolio_design.include_erb("test_procedures.adoc.erb", { "db_obj" => ext, "org" => "appendix"}) %> +<% end # if gen_ctp_content -%> + +<% end # do ext_req -%> diff --git a/backends/portfolio/templates/idl_func_appendix.adoc.erb b/backends/portfolio/templates/idl_func_appendix.adoc.erb new file mode 100644 index 000000000..88b2d45d2 --- /dev/null +++ b/backends/portfolio/templates/idl_func_appendix.adoc.erb @@ -0,0 +1,40 @@ +<<< +[appendix] +== IDL Function Details + +<% design.functions.each do |f| -%> +<%= anchor_for_udb_doc_idl_func(f.name) %> +=== <%= f.name %><% if f.builtin? -%> (builtin)<% end -%> + +<%= f.description %> + +[cols="1,2"] +|=== +h| Return Type +a| +[source,idl] +---- +<%= f.return_type_list_str.join(', ') %> +---- + +h| Arguments +a| +<% if f.arguments_list_str.empty? -%> +None +<% else -%> +[source,idl] +---- +<%= f.arguments_list_str.join (', ') %> +---- +<% end -%> +|=== + +<% unless f.builtin? -%> +<% body_ast = f.body -%> +[source,idl,subs="specialchars,macros"] +---- +<%= body_ast.gen_adoc %> +---- +<% end -%> + +<% end -%> diff --git a/backends/portfolio/templates/inst_appendix.adoc.erb b/backends/portfolio/templates/inst_appendix.adoc.erb new file mode 100644 index 000000000..0f6f809c8 --- /dev/null +++ b/backends/portfolio/templates/inst_appendix.adoc.erb @@ -0,0 +1,140 @@ +<<< +[appendix] +== Instruction Details + +<% portfolio_design.in_scope_instructions.each do |inst| -%> +<<< +<%= anchor_for_udb_doc_inst(inst.name) %> +=== <%= inst.name %> + +*<%= inst.long_name %>* + +This instruction is defined by: + +<%= inst.defined_by_condition.to_asciidoc %> + +==== Encoding + +<% if inst.multi_encoding? -%> +[NOTE] +This instruction has different encodings in RV32 and RV64. + +[tabs] +==== +RV32:: ++ +[wavedrom, ,svg,subs='attributes',width="100%"] +.... +<%= JSON.dump inst.wavedrom_desc(32) %> +.... + +RV64:: ++ +[wavedrom, ,svg,subs='attributes',width="100%"] +.... +<%= JSON.dump inst.wavedrom_desc(64) %> +.... +==== +<% else -%> +[wavedrom, ,svg,subs='attributes',width="100%"] +.... +<%= JSON.dump inst.wavedrom_desc(inst.base.nil? ? 32 : inst.base) %> +.... +<% end -%> + +==== Synopsis + +<%= inst.description %> + +==== Access +<% if portfolio_design.in_scope_extensions.any? { |e| e.name == "H" } -%> +[cols="^,^,^,^,^"] +<% else -%> +[cols="^,^,^"] +<% end -%> +|=== +| M | <% if portfolio_design.in_scope_extensions.any? { |e| e.name == "H" } -%>HS<% else -%>S<% end -%> | U <% if portfolio_design.in_scope_extensions.any? { |e| e.name == "H" } -%> | VS | VU <% end -%> + +| [.access-always]#Always# +| [.access-<%=inst.access['s']%>]#<%= inst.access['s'].capitalize %># +| [.access-<%=inst.access['u']%>]#<%= inst.access['u'].capitalize %># +<% if portfolio_design.in_scope_extensions.any? { |e| e.name == "H" } %> +| [.access-<%=inst.access['vs']%>]#<%= inst.access['vs'].capitalize %># +| [.access-<%=inst.access['vu']%>]#<%= inst.access['vu'].capitalize %># +<% end %> +|=== + +<% if inst.access_detail? -%> +<%= inst.access_detail %> +<% end -%> + +==== Decode Variables + +<% if inst.multi_encoding? -%> +[tabs] +==== +RV32:: ++ +[source.idl] +---- +<% inst.decode_variables(32).each do |d| -%> +<%= d.sext? ? 'signed ' : '' %>Bits<<%= d.size %>> <%= d.name %> = <%= d.extract %>; +<% end -%> +---- + +RV64:: ++ +[source,idl] +---- +<% inst.decode_variables(64).each do |d| -%> +<%= d.sext? ? 'signed ' : '' %>Bits<<%= d.size %>> <%= d.name %> = <%= d.extract %>; +<% end -%> +---- +==== +<% else -%> +[source,idl] +---- +<% inst.decode_variables(inst.base.nil? ? 32 : inst.base).each do |d| -%> +<%= d.sext? ? 'signed ' : '' %>Bits<<%= d.size %>> <%= d.name %> = <%= d.extract %>; +<% end -%> +---- +<% end -%> + +<% if inst.key?("operation()") -%> +==== IDL Operation + +[source,idl,subs="specialchars,macros"] +---- +<%= inst.operation_ast(design.symtab).gen_adoc %> +---- +<% end -%> + +<% if inst.key?("sail()") -%> +==== Sail Operation + +[source,sail] +---- +<%= inst["sail()"] %> +---- +<% end -%> + +==== Exceptions + +<% exception_list = inst.reachable_exceptions_str(design.symtab) -%> +<% if exception_list.empty? -%> +This instruction does not generate synchronous exceptions. +<% else -%> +This instruction may result in the following synchronous exceptions: + + <% exception_list.sort.each do |etype| -%> + * <%= etype %> + <% end -%> + +<% end -%> + +<% if defined?(gen_ctp_content) && gen_ctp_content -%> +<%= portfolio_design.include_erb("coverage_points.adoc.erb", { "db_obj" => inst, "org" => "appendix" }) %> +<%= portfolio_design.include_erb("test_procedures.adoc.erb", { "db_obj" => inst, "org" => "appendix" }) %> +<% end # if gen_ctp_content -%> + +<% end # each in_scope instruction -%> diff --git a/backends/portfolio/templates/test_procedures.adoc.erb b/backends/portfolio/templates/test_procedures.adoc.erb new file mode 100644 index 000000000..7c8ff955f --- /dev/null +++ b/backends/portfolio/templates/test_procedures.adoc.erb @@ -0,0 +1,31 @@ +<% unless db_obj.cert_test_procedures.empty? -%> +<% nice_name = db_obj.is_a?(CsrField) ? "#{db_obj.parent.name}.#{db_obj.name}" : db_obj.name -%> +<% if defined?(use_description_list) && use_description_list -%> +Test Procedures for `<%= nice_name %>`: :: +<% else -%> +==== Test Procedures for `<%= nice_name %>` +<% end -%> + +<% db_obj.cert_test_procedures.each do |tp| -%> +[%autowidth] +.*ID <%= tp.id %>* Test Procedure +|=== + +| *Name* | <%= tp.name %> +| *Description* | <%= tp.description %> +| *Coverage Points* +a| +<% tp.cert_coverage_points.each do |cp| -%> +* <%= defined?(org) ? link_to_udb_doc_cov_pt(org, cp.id) : cp.id %> +<% end # each cp -%> +| *Steps* +a| +<% tp.cert_steps.each do |step| -%> +. <%= step.name %> +.. <%= step.description %> +<%= step.note.nil? ? "" : "[NOTE]\n" + step.note %> +<% end # each step -%> +<% end # each test procedure -%> +|=== + +<% end # unless no test plans -%> diff --git a/backends/proc_cert/README.adoc b/backends/proc_cert/README.adoc new file mode 100644 index 000000000..6bd20e832 --- /dev/null +++ b/backends/proc_cert/README.adoc @@ -0,0 +1,2 @@ +This certification backend isn't a real backend. +Instead, it contains common Rake code and ERB templates shared by multiple processor certification backends. diff --git a/backends/proc_cert/tasks.rake b/backends/proc_cert/tasks.rake new file mode 100644 index 000000000..b2c80736e --- /dev/null +++ b/backends/proc_cert/tasks.rake @@ -0,0 +1,35 @@ +# frozen_string_literal: true +# +# Contains common methods called from certification backend tasks.rake files. + +require "pathname" +require "asciidoctor-pdf" +require "asciidoctor-diagram" + +# @param erb_template_pname [String] Path to ERB template file +# @param target_pname [String] Full name of adoc file being generated +# @param model_name [String] Name of the processor certificate model +def proc_cert_create_adoc(erb_template_pname, target_pname, model_name) + arch = pf_create_arch + + # Create ProcCertModel for specific processor certificate model as specified in its arch YAML file. + # The Architecture object also creates all other portfolio-related class instances from their arch YAML files. + # None of these objects are provided with a Design object when created. + puts "UPDATE: Creating ProcCertModel object for #{model_name}" + proc_cert_model = arch.proc_cert_model(model_name) + proc_cert_class = proc_cert_model.proc_cert_class + + # Create the one ProcCertDesign object required for the ERB evaluation. + puts "UPDATE: Creating ProcCertDesign object using processor certificate model #{model_name}" + proc_cert_design = ProcCertDesign.new(model_name, arch, ProcCertDesign.proc_ctp_type, proc_cert_model, proc_cert_class) + + # Create empty binding and then specify explicitly which variables the ERB template can access. + # Seems to use this method name in stack backtraces (hence its name). + def evaluate_erb + binding + end + erb_binding = evaluate_erb + proc_cert_design.init_erb_binding(erb_binding) + + pf_create_adoc(erb_template_pname, erb_binding, target_pname, proc_cert_design) +end diff --git a/backends/proc_cert/templates/README.adoc b/backends/proc_cert/templates/README.adoc new file mode 100644 index 000000000..f577b4556 --- /dev/null +++ b/backends/proc_cert/templates/README.adoc @@ -0,0 +1 @@ +This directory contains partial ERB templates shared by multiple processor certification-based backends. diff --git a/backends/proc_cert/templates/glossary.adoc.erb b/backends/proc_cert/templates/glossary.adoc.erb new file mode 100644 index 000000000..ec2566df2 --- /dev/null +++ b/backends/proc_cert/templates/glossary.adoc.erb @@ -0,0 +1,20 @@ +.Glossary +[%autowidth] +|=== +| Term | Meaning + +| RISC-V | (Reduced Instruction Set Computer) architecture, version 5 +| RVI | RISC-V International (organization that oversees RISC-V) +| RVCP | RISC-V Certification Program +| TSC | Technical Steering Committee (branch of RVI that creates standards) +| CSC | Certification Steering Committee (branch of RVI that oversees the RVCP) +| CRD | Certification Requirements Document +| CTP | Certification Test Plan +| CSR | Control & Status Register (located inside processor, not memory-mapped) +| TBD | To Be Determined +| N/A | “Not Applicable” +| AKA | “Also Known As” +| Must | Indicates a mandatory requirement +| Should | Indicates a recommended requirement +| May | Indicates an optional requirement +|=== diff --git a/backends/proc_cert/templates/priv_modes.adoc.erb b/backends/proc_cert/templates/priv_modes.adoc.erb new file mode 100644 index 000000000..58d8c3eb8 --- /dev/null +++ b/backends/proc_cert/templates/priv_modes.adoc.erb @@ -0,0 +1,12 @@ +=== Privileged Modes + +|=== +| M | S | U | VS | VU + +| <% if proc_cert_class.mandatory_priv_modes.include?('M') -%> MANDATORY <% else -%> OUT-OF-SCOPE <% end -%> +| <% if proc_cert_class.mandatory_priv_modes.include?('S') -%> MANDATORY <% else -%> OUT-OF-SCOPE <% end -%> +| <% if proc_cert_class.mandatory_priv_modes.include?('U') -%> MANDATORY <% else -%> OUT-OF-SCOPE <% end -%> +| <% if proc_cert_class.mandatory_priv_modes.include?('VS') -%> MANDATORY <% else -%> OUT-OF-SCOPE <% end -%> +| <% if proc_cert_class.mandatory_priv_modes.include?('VU') -%> MANDATORY <% else -%> OUT-OF-SCOPE <% end -%> + +|=== diff --git a/backends/proc_cert/templates/proc_naming_scheme.adoc.erb b/backends/proc_cert/templates/proc_naming_scheme.adoc.erb new file mode 100644 index 000000000..2cb012054 --- /dev/null +++ b/backends/proc_cert/templates/proc_naming_scheme.adoc.erb @@ -0,0 +1,14 @@ +RVCP components for processors have the following format for their : + + [<-base>] + +Where: + +* is MC for Microcontroller Class and AC for Apps-processor Class +* is 3-digit integer defined as follows: +** The hundreds's digit indicates the series +** The ten's digit identifies large differences in mandatory extensions (e.g., V, H) within the series +** The one's digit indentifies small/medium differences in mandatory extensions (e.g., Zicond, PMP) within the series +* is optional and is 32 for RV32I, 64 for RV64I, and 32E for RV32E +** If multiple bases are supported and is omitted in a reference, the reference applies to all bases +** If only one base is supported then is generally omitted diff --git a/backends/proc_cert/templates/related_specs.adoc.erb b/backends/proc_cert/templates/related_specs.adoc.erb new file mode 100644 index 000000000..2d2a12979 --- /dev/null +++ b/backends/proc_cert/templates/related_specs.adoc.erb @@ -0,0 +1,12 @@ +=== Related Specifications + +[cols="2,2,3,3,3"] +|=== +| Certificate Model | TSC Profile | Unpriv ISA Manual | Priv ISA Manual | Debug Manual + +| <%= proc_cert_model.name %> +| <%= proc_cert_model.tsc_profile.nil? ? "No profile" : proc_cert_model.tsc_profile.marketing_name %> +| <%= proc_cert_model.unpriv_isa_manual_revision %> +| <%= proc_cert_model.priv_isa_manual_revision %> +| <%= proc_cert_model.debug_manual_revision %> +|=== diff --git a/backends/proc_cert/templates/rev_history.adoc.erb b/backends/proc_cert/templates/rev_history.adoc.erb new file mode 100644 index 000000000..f8da71d24 --- /dev/null +++ b/backends/proc_cert/templates/rev_history.adoc.erb @@ -0,0 +1,14 @@ +History of documentation changes that eventually lead to releases. + +[cols="1,1,5"] +|=== +| Date | Revision | Changes + +<% proc_cert_model.revision_history.each do |rev| -%> +| <%= rev.date %> +| <%= rev.revision %> +a| <% rev.changes.each do |change| %> +* <%= change %> +<% end -%> +<% end -%> +|=== diff --git a/backends/proc_cert/templates/rvcp_naming_scheme.adoc.erb b/backends/proc_cert/templates/rvcp_naming_scheme.adoc.erb new file mode 100644 index 000000000..d0ad65289 --- /dev/null +++ b/backends/proc_cert/templates/rvcp_naming_scheme.adoc.erb @@ -0,0 +1,22 @@ +All components of the RVCP share the following naming scheme: + + Format: [v] + +Where: + +* Left & right square braces denote optional. +* Less-than & greater-than signs just separate fields (i.e., they aren't present in the CRD name). +* identifies the type of RISC-V standard (processor, non-processor system IP, or platform) along with + any other information required to identify the variant of that standard. +* identifies a particular release +** Format is [.[.]] +** Inspired by semantic versioning scheme (https://semver.org/) but doesn't follow it exactly + +The rules for updating , , and are defined by the type of RVCP component. +However, the follow these general guidelines: + +* A release of 0 is used for pre-release versions and release versions start with 1. +* The release number is updated when mandatory changes are made. +* The release number is updated when optional changes are made. +* The release number is updated for documentation fixes/improvements that don't affect + certificates already obtained for a particular implementation. diff --git a/backends/proc_cert/templates/typographic.adoc.erb b/backends/proc_cert/templates/typographic.adoc.erb new file mode 100644 index 000000000..7b1aacdde --- /dev/null +++ b/backends/proc_cert/templates/typographic.adoc.erb @@ -0,0 +1,17 @@ +CSR field colors:: + +* Grey fields are reserved (WPRI) +* Green fields are present +* Red fields are defined by the RISC-V ISA but not present + +CSR field types:: + +[%autowidth] +|=== +| Abbreviation | Description + +<% CsrField::TYPE_DESC_MAP.each do |abbreviation, description| -%> +| <%= abbreviation %> +| <%= description %> +<% end -%> +|=== diff --git a/backends/proc_crd/tasks.rake b/backends/proc_crd/tasks.rake new file mode 100644 index 000000000..750cad6ba --- /dev/null +++ b/backends/proc_crd/tasks.rake @@ -0,0 +1,93 @@ +# frozen_string_literal: true +# +# Contains Rake rules to generate adoc, PDF, and HTML for a CRD. + +require "pathname" + +PROC_CRD_DOC_DIR = Pathname.new "#{$root}/backends/proc_crd" + +Dir.glob("#{$root}/arch/proc_cert_model/*.yaml") do |f| + model_name = File.basename(f, ".yaml") + model_obj = YAML.load_file(f, permitted_classes: [Date]) + class_name = File.basename(model_obj['class']['$ref'].split("#")[0], ".yaml") + raise "Ill-formed processor certificate model file #{f}: missing 'class' field" if model_obj['class'].nil? + + file "#{$root}/gen/proc_crd/adoc/#{model_name}-CRD.adoc" => [ + __FILE__, + "#{$root}/arch/proc_cert_class/#{class_name}.yaml", + "#{$root}/arch/proc_cert_model/#{model_name}.yaml", + "#{$root}/lib/arch_obj_models/certificate.rb", + "#{$root}/lib/arch_obj_models/portfolio.rb", + "#{$root}/lib/portfolio_design.rb", + "#{$root}/lib/design.rb", + "#{$root}/backends/portfolio/templates/ext_appendix.adoc.erb", + "#{$root}/backends/portfolio/templates/inst_appendix.adoc.erb", + "#{$root}/backends/portfolio/templates/csr_appendix.adoc.erb", + "#{$root}/backends/portfolio/templates/beginning.adoc.erb", + "#{$root}/backends/proc_cert/templates/typographic.adoc.erb", + "#{$root}/backends/proc_cert/templates/rev_history.adoc.erb", + "#{$root}/backends/proc_cert/templates/related_specs.adoc.erb", + "#{$root}/backends/proc_cert/templates/priv_modes.adoc.erb", + "#{$root}/backends/proc_cert/templates/rev_history.adoc.erb", + "#{PROC_CRD_DOC_DIR}/templates/proc_crd.adoc.erb" + ] do |t| + proc_cert_create_adoc("#{PROC_CRD_DOC_DIR}/templates/proc_crd.adoc.erb", t.name, model_name) + end + + file "#{$root}/gen/proc_crd/pdf/#{model_name}-CRD.pdf" => [ + __FILE__, + "#{$root}/gen/proc_crd/adoc/#{model_name}-CRD.adoc" + ] do |t| + pf_adoc2pdf("#{$root}/gen/proc_crd/adoc/#{model_name}-CRD.adoc", t.name) + end + + file "#{$root}/gen/proc_crd/html/#{model_name}-CRD.html" => [ + __FILE__, + "#{$root}/gen/proc_crd/adoc/#{model_name}-CRD.adoc" + ] do |t| + pf_adoc2html("#{$root}/gen/proc_crd/adoc/#{model_name}-CRD.adoc", t.name) + end +end + +namespace :gen do + desc <<~DESC + Generate Processor CRD (Certification Requirements Document) as a PDF. + + Required options: + model_name - The name of the certification model under arch/proc_cert_model + DESC + task :proc_crd_pdf, [:model_name] do |_t, args| + model_name = args[:model_name] + if model_name.nil? + warn "Missing required option: 'model_name'" + exit 1 + end + + unless File.exist?("#{$root}/arch/proc_cert_model/#{model_name}.yaml") + warn "No certification model named '#{model_name}' found in arch/proc_cert_model" + exit 1 + end + + Rake::Task["#{$root}/gen/proc_crd/pdf/#{model_name}-CRD.pdf"].invoke + end + + desc <<~DESC + Generate Processor CRD (Certification Requirements Document) as an HTML file. + + Required options: + model_name - The name of the certification model under arch/proc_cert_model + DESC + task :proc_crd_html, [:model_name] do |_t, args| + if args[:model_name].nil? + warn "Missing required option: 'model_name'" + exit 1 + end + + unless File.exist?("#{$root}/arch/proc_cert_model/#{args[:model_name]}.yaml") + warn "No certification model named '#{args[:model_name]}' found in arch/proc_cert_model" + exit 1 + end + + Rake::Task["#{$root}/gen/proc_crd/html/#{args[:model_name]}-CRD.html"].invoke + end +end diff --git a/backends/proc_crd/templates/proc_crd.adoc.erb b/backends/proc_crd/templates/proc_crd.adoc.erb new file mode 100644 index 000000000..5a591f20a --- /dev/null +++ b/backends/proc_crd/templates/proc_crd.adoc.erb @@ -0,0 +1,349 @@ +<%= portfolio_design.include_erb("beginning.adoc.erb") %> + += <%= proc_cert_model.name %> Processor Certification Requirements Document + +[Preface] +== CRD Revision History + +<%= proc_cert_design.include_erb("rev_history.adoc.erb") %> + +[Preface] +== Typographic Conventions + +<%= proc_cert_design.include_erb("typographic.adoc.erb") %> + +[Preface] +== Glossary + +<%= proc_cert_design.include_erb("glossary.adoc.erb") %> + +== Introduction + +<%= proc_cert_model.introduction %> + +<%= proc_cert_class.introduction %> + +=== What's a CRD? + +Certification Requirements Documents (CRDs) list requirements an implementation must meet +to obtain an associated RVI (RISC-V International) certificate. +CRDs are developed by the RVI CSC (Certification Steering Committee) organization in collaboration +with the RVI TSC (Technical Steering Committee) organization who creates RISC-V standards. + +The CRDs refer to and augment information provided in existing ratified RVI standards. + +There are a variety of certificates offered by RVI to accomodate the various RVI standards. +There are certificates for processors, non-processor system IP (e.g., IOMMU), +and system platforms (processor + system IP) hardware standards. +There are multiple classes of processor certificates available to accomodate the wide range of +RISC-V implementations from basic microcontrollers to advanced Applications-class processors. + +Each CRD has a list of mandatory behaviors along with a list of optional behaviors. +Note that not all behaviors allowed in RISC-V standards are supported by a particular CRD. + +=== Naming Scheme + +==== CRD Naming + +<%= proc_cert_design.include_erb("rvcp_naming_scheme.adoc.erb") %> + +The specific rules for updating the version number of a CRD are as follows: + +* The release number is updated for changes that *could* cause a previously certified + implementation to no longer meet requirements. An example is requiring a new version of a standard. +* The release number is updated for increased support of optional behaviors. +* The release number is updated for documentation fixes/improvements. + These changes *cannot* cause a previously certified implementation to no longer meet requirements. + +==== Processor Naming + +<%= proc_cert_design.include_erb("proc_naming_scheme.adoc.erb") %> + +=== Requirements Terminology + +.Requirement Types +[%autowidth] +|=== +| Term | Meaning + +| MANDATORY | You have to implement it to get a certificate and the certificate tests will cover it +| OPTIONAL | It's up to you if you implement or not. If you claim to implement it, certificate tests will cover it +| IN-SCOPE | Either MANDATORY or OPTIONAL +| OUT-OF-SCOPE | It's up to you if you implement or not. If you implement it, it won't be certified but make sure you don't mess up anything we are certifying. +| INCOMPATIBLE | If you implement it you won't get a certificate +|=== + +.Definition of CSR Fields +[%autowidth] +|=== +| Field Type | Read Value After Writing Illegal Value | Read Value Function Of | Illegal Instruction Exception | Priv ISA Manual Quote + +| WLRL | Any deterministic legal or illegal value | Value before write and illegal value written | Optional +| Implementations are permitted but not required to raise an illegal-instruction exception if an instruction attempts to write a non-supported value to a WLRL field. Implementations can return arbitrary bit patterns on the read of a WLRL field when the last write was of an illegal value, but the value returned should deterministically depend on the illegal written value and the value of the field prior to the write. +| WARL | Any deterministic legal value | Any architectural hart state | Prohibited +| Implementations will not raise an exception on writes of unsupported values to a WARL field. Implementations can return any legal value on the read of a WARL field when the last write was of an illegal value, but the legal value returned should deterministically depend on the illegal written value and the architectural state of the hart. +| WPRI | 0 | Nothing | Not specified +| Some whole read/write fields are reserved for future use. Implementations that do not furnish these fields must make them read-only zero. +|=== + +*WARL (Write Anything, Read Legal)*: + +The Priv ISA requires reads of WARL fields to return some implementation-dependent deterministic legal value +after the field is written with an illegal value. +Certifying such behaviors is expensive and provides low value for a certificate since software can't rely +on a particular behavior from one implementation to another. + +Processor CRDs define writes to WARL fields of illegal values to be OUT-OF-SCOPE unless otherwise stated +(i.e., certification tests will only ever write legal values to WARL fields except for the special cases listed below). +When not OUT-OF-SCOPE, the required behavior is defined as this might be more constrained in implementations than +in the standard. + +The following special cases for WARL are supported when explicitly listed in the corresponding CRD CSR field requirements: + +1. Probing for Field Width + +* Some WARL fields are variable length such as the ASID field in the virtual memory extension. +* Here's the algorithm recommended to discover the ASID width: +** The number of implemented ASID bits, termed ASIDLEN, may be determined by writing one to every bit position in + the ASID field, then reading back the value in the satp CSR to see which bit positions in the ASID field hold a one. +* The RVCP-provided certification materials (certification tests, certification reference models) can map writes of + illegal values to the ASID field to the corresponding read value as long as they are provided the ASIDLEN value + for an implementation. + +2. Probing for Options + +* E.g., Writable misa bits + +3. Allowed values are a function of extension presence and/or their parameters + +* E.g., satp.mode legal write values + +*WLRL (Write Legal, Read Legal)*: + +The Priv ISA requires reads of WLRL fields to return some implementation-dependent deterministic arbitrary value +after the field is written with an illegal value. +Certifying such behaviors is expensive and provides low value for a certificate since software can't rely +on a particular behavior. +Processor CRDs define writes to WLRL fields of illegal values to be OUT-OF-SCOPE unless otherwise stated +(i.e., certification tests will only ever write legal values to WLRL fields). + +*WPRI (Write Preserve, Read Ignore)*: + +The Priv ISA requires reads of WPRI fields to return a value of 0. +Such WPRI fields are always unimplemented by definition. +Certification tests are aware of which fields in the CSRs are WPRI and normally write them with 0 but will +also write them with ~0 (all ones) and ensure that reads return 0 in both cases. +It is OUT-OF-SCOPE for certification tests to write all possible values of WPRI fields +(especially if they are more than just a few bits) and certification tests aren't intended to be comprehensive +verification test suites anyways. + +<%= proc_cert_design.include_erb("related_specs.adoc.erb") %> +<%= proc_cert_design.include_erb("priv_modes.adoc.erb") %> +<<< +== Extensions + +Any RISC-V extensions not listed in this section are OUT-OF-SCOPE. +The <%= proc_cert_model.name %> certificate doesn't cover their behaviors. + +<% ExtensionPresence.presence_types_obj.each do |presence_obj| -%> + +=== <%= presence_obj.to_s.capitalize %> Extensions + +<% ext_reqs = proc_cert_model.in_scope_ext_reqs(presence_obj) -%> +<% if ext_reqs.empty? -%> +None +<% else -%> +[%autowidth] +|=== +| Requirement ID | Extension | Version | Long Name | Note + +<% ext_reqs.each do |ext_req| -%> +<% ext = arch.extension(ext_req.name) -%> +| <%= ext_req.req_id %> +| <%= link_to_udb_doc_ext(ext_req.name) %> +| <%= ext_req.requirement_specs.map(&:to_s).join(", ") %> +| <%= ext.nil? ? "" : ext.long_name %> +| <%= ext_req.note.nil? ? "" : ext_req.note %> +<% end # each ext_req -%> +|=== +<% end # if empty ext_reqs -%> + +<% proc_cert_model.extra_notes_for_presence(presence_obj)&.each do |extra_note| -%> +NOTE: <%= extra_note.text %> + +<% end # each extra_note -%> + +<% end # each possible presence -%> + +<% unless proc_cert_model.recommendations.empty? -%> +=== Recommendations + +Recommendations are not strictly mandated but are included to guide implementers. + +<% proc_cert_model.recommendations.each do |recommendation| -%> +<%= recommendation.text %> +<% end # each recommendation -%> +<% end # unless recommendations empty -%> + +<<< +== Implementation-dependencies + +RISC-V standards support many implementation-defined parameters. In many cases, there +are no names associated with these parameters. Names are defined in this section when +not provided in the associated standard. + +=== IN-SCOPE Parameters + +These implementation-dependent options defined by MANDATORY or OPTIONAL extensions are IN-SCOPE. +An implementation must abide by the "Allowed Value(s)" to obtain a certificate. +If the "Allowed Value(s)" is "Any" then any value allowed by the type is acceptable. + +<% if portfolio_design.all_in_scope_params.empty? -%> +None +<% else -%> +[cols="4,2,1,1,2"] +|=== +| Parameter | Type | Allowed Value(s) | Extension(s) | Note + +<% design.all_in_scope_params.each do |in_scope_param| -%> +<% param = in_scope_param.param -%> +<% in_scope_exts = portfolio_design.all_in_scope_exts_with_param(param) -%> +| <%= param.name_potentially_with_link(in_scope_exts) %> +| <%= param.schema_type %> +| <%= in_scope_param.allowed_values %> +| <%= in_scope_exts.map { |ext| link_to_udb_doc_ext_param(ext.name, param.name, ext.name) }.join(", ") %> +a| <%= in_scope_param.note %> +<% end # do -%> +|=== +<% end # if table -%> + +=== OUT-OF-SCOPE Parameters + +These implementation-dependent options defined by MANDATORY or OPTIONAL extensions are OUT-OF-SCOPE. +There are no restrictions on their values for certification purposes because the certificate +doesn't cover the behavior of the associated RISC-V standard as a function of these parameters. + +<% if portfolio_design.all_out_of_scope_params.empty? -%> +None +<% else -%> +[%autowidth] +|=== +| Parameters | Type | Extension(s) + +<% portfolio_design.all_out_of_scope_params.each do |param| -%> +<% exts = portfolio_design.all_in_scope_exts_without_param(param) -%> +| <%= param.name_potentially_with_link(exts) %> +| <%= param.schema_type %> +| <%= exts.map { |ext| link_to_udb_doc_ext_param(ext.name, param.name, ext.name) }.join(", ") %> + +<% end # do -%> +|=== +<% end # if table -%> + +== Traps + +RISC-V supports both synchronous exceptions and asynchronous interrupts. +TODO: List only traps that exist in this processor certificate model (currently lists all possible in present extensions). +See https://github.com/riscv-software-src/riscv-unified-db/issues/291 and https://github.com/riscv-software-src/riscv-unified-db/issues/324 +TODO: Show traps per privilege mode + +=== Synchronous Exceptions + +|=== +| `xcause.CODE` CSR Field Value | Name +<% design.in_scope_exception_codes.sort_by{ |code| code.num }.each do |code| -%> +| <%= code.num %> | <%= code.name %> +<% end -%> +|=== + +=== Asynchronous Interrupts + +|=== +| `xcause.CODE` CSR Field Value | Name +<% design.in_scope_interrupt_codes.sort_by{ |code| code.num }.each do |code| -%> +| <%= code.num %> | <%= code.name %> +<% end -%> +|=== + +== Instruction Summary + +[%autowidth] +|=== +| Name | Long Name + +<% design.in_scope_instructions.each do |inst| -%> +| <%= link_to_udb_doc_inst(inst.name) %> +| <%= inst.long_name %> +<% end # do -%> +|=== + +== CSR Summary + +=== By Name + +[%autowidth] +|=== +| Name | Long Name | Address | Mode | Primary Extension + +<% design.in_scope_csrs.sort_by!(&:name).each do |csr| -%> +| <%= link_to_udb_doc_csr(csr.name) %> +| <%= csr.long_name %> +| <%= "0x#{csr.address.to_s(16)}" %> +| <%= csr.priv_mode %> +| <%= csr.primary_defined_by %> +<% end # do -%> +|=== + +=== By Address + +[%autowidth] +|=== +| Address | Mode | Name | Long Name | Primary Extension + +<% design.in_scope_csrs.sort_by!(&:address).each do |csr| -%> +| <%= "0x#{csr.address.to_s(16)}" %> +| <%= csr.priv_mode %> +| <%= link_to_udb_doc_csr(csr.name) %> +| <%= csr.long_name %> +| <%= csr.primary_defined_by %> +<% end # do -%> +|=== + +<% unless proc_cert_model.requirement_groups.empty? -%> +== Additional Requirements + +This section contains requirements in addition to those already specified related to extensions and parameters. +These additional requirements are organized as groups of related requirements. + +<% proc_cert_model.requirement_groups.each do |group| -%> +=== <%= group.name %> + +<%= group.description %> + +<% unless group.when.nil? -%> +[IMPORTANT] +<%= group.name %> requirements only apply when <%= group.when_pretty %>. +<% end -%> + +[%autowidth] +|=== +| Req Number | Description + +<% group.requirements.each do |req| -%> +| <%= req.name %> +a| <%= req.description %> +<% unless req.when.nil? -%> +[IMPORTANT] +Requirement <%= req.name %> only apply when <%= req.when_pretty %>. +<% end -%> +<% end -%> +|=== + +<% end -%> +<% end # unless requirement_groups.empty? -%> + +// Appendices +<%= portfolio_design.include_erb("ext_appendix.adoc.erb") %> +<%= portfolio_design.include_erb("inst_appendix.adoc.erb") %> +<%= portfolio_design.include_erb("csr_appendix.adoc.erb") %> +<%= portfolio_design.include_erb("idl_func_appendix.adoc.erb") %> diff --git a/backends/proc_ctp/tasks.rake b/backends/proc_ctp/tasks.rake new file mode 100644 index 000000000..b33460c7f --- /dev/null +++ b/backends/proc_ctp/tasks.rake @@ -0,0 +1,122 @@ +# frozen_string_literal: true +# +# Contains Rake rules to generate adoc, PDF, and HTML for a CTP (Certification Test Plan). + +require "pathname" + +PROC_CTP_DOC_DIR = Pathname.new "#{$root}/backends/proc_ctp" +PROC_CTP_GEN_DIR = $root / "gen" / "proc_ctp" +PROC_CTP_ISA_MAN_DIR = "#{PROC_CTP_GEN_DIR}/adoc/ext/riscv-isa-manual" +PROC_CTP_DOCS_RESOURCES_DIR = "#{PROC_CTP_ISA_MAN_DIR}/docs-resources" + +Dir.glob("#{$root}/arch/proc_cert_model/*.yaml") do |f| + model_name = File.basename(f, ".yaml") + model_obj = YAML.load_file(f, permitted_classes: [Date]) + class_name = File.basename(model_obj['class']['$ref'].split("#")[0], ".yaml") + raise "Ill-formed processor certificate model file #{f}: missing 'class' field" if model_obj['class'].nil? + + file "#{PROC_CTP_GEN_DIR}/adoc/#{model_name}-CTP.adoc" => [ + __FILE__, + "#{$root}/arch/proc_cert_class/#{class_name}.yaml", + "#{$root}/arch/proc_cert_model/#{model_name}.yaml", + "#{$root}/lib/arch_obj_models/certificate.rb", + "#{$root}/lib/arch_obj_models/portfolio.rb", + "#{$root}/lib/portfolio_design.rb", + "#{$root}/lib/design.rb", + "#{$root}/backends/portfolio/templates/ext_appendix.adoc.erb", + "#{$root}/backends/portfolio/templates/inst_appendix.adoc.erb", + "#{$root}/backends/portfolio/templates/csr_appendix.adoc.erb", + "#{$root}/backends/portfolio/templates/beginning.adoc.erb", + "#{$root}/backends/portfolio/templates/coverage_points.adoc.erb", + "#{$root}/backends/portfolio/templates/test_procedures.adoc.erb", + "#{$root}/backends/proc_cert/templates/typographic.adoc.erb", + "#{$root}/backends/proc_cert/templates/rev_history.adoc.erb", + "#{$root}/backends/proc_cert/templates/related_specs.adoc.erb", + "#{$root}/backends/proc_cert/templates/priv_modes.adoc.erb", + "#{PROC_CTP_DOC_DIR}/templates/proc_ctp.adoc.erb" + ] do |t| + # Ensure that the required submodule repositories are up-to-date. + # TODO: Commented this out since it puts the submodule in the HEADless state + # and doesn't contain the latest updates to "main" from the submodules. + #sh "git submodule update --init ext/csc-riscv-isa-manual 2>&1" + #sh "git submodule update --init ext/docs-resources 2>&1" + + # Pull in the latest version of the csc-riscv-isa-manual. + sh "cd ext/csc-riscv-isa-manual; git fetch; git merge origin/main 2>&1" + + # Pull in the latest version of the docs-resources. + #sh "cd ext/docs-resources; git fetch; git merge origin/main 2>&1" + + + # Use git archive to extract the latest version of the csc-riscv-isa-manual. + FileUtils.mkdir_p "#{PROC_CTP_ISA_MAN_DIR}" + Dir.chdir($root / "ext" / "csc-riscv-isa-manual") do + sh "git archive --format=tar HEAD | tar xf - -C #{PROC_CTP_ISA_MAN_DIR}" + end + + # Use git archive to extract the latest version of the docs-resources. + FileUtils.mkdir_p "#{PROC_CTP_DOCS_RESOURCES_DIR}" + Dir.chdir($root / "ext" / "docs-resources") do + sh "git archive --format=tar HEAD | tar xf - -C #{PROC_CTP_DOCS_RESOURCES_DIR}" + end + + proc_cert_create_adoc("#{PROC_CTP_DOC_DIR}/templates/proc_ctp.adoc.erb", t.name, model_name) + end + + file "#{$root}/gen/proc_ctp/pdf/#{model_name}-CTP.pdf" => [ + __FILE__, + "#{$root}/gen/proc_ctp/adoc/#{model_name}-CTP.adoc" + ] do |t| + pf_adoc2pdf("#{$root}/gen/proc_ctp/adoc/#{model_name}-CTP.adoc", t.name) + end + + file "#{$root}/gen/proc_ctp/html/#{model_name}-CTP.html" => [ + __FILE__, + "#{$root}/gen/proc_ctp/adoc/#{model_name}-CTP.adoc" + ] do |t| + pf_adoc2html("#{$root}/gen/proc_ctp/adoc/#{model_name}-CTP.adoc", t.name) + end +end + +namespace :gen do + desc <<~DESC + Generate Processor CTP (Certification Test Plan) as a PDF. + + Required options: + model_name - The name of the certification model under arch/proc_cert_model + DESC + task :proc_ctp_pdf, [:model_name] do |_t, args| + model_name = args[:model_name] + if model_name.nil? + warn "Missing required option: 'model_name'" + exit 1 + end + + unless File.exist?("#{$root}/arch/proc_cert_model/#{model_name}.yaml") + warn "No certification model named '#{model_name}' found in arch/proc_cert_model" + exit 1 + end + + Rake::Task["#{$root}/gen/proc_ctp/pdf/#{model_name}-CTP.pdf"].invoke + end + + desc <<~DESC + Generate Processor CTP (Certification Test Plan) as an HTML file. + + Required options: + model_name - The name of the certification model under arch/proc_cert_model + DESC + task :proc_ctp_html, [:model_name] do |_t, args| + if args[:model_name].nil? + warn "Missing required option: 'model_name'" + exit 1 + end + + unless File.exist?("#{$root}/arch/proc_cert_model/#{args[:model_name]}.yaml") + warn "No certification model named '#{args[:model_name]}' found in arch/proc_cert_model" + exit 1 + end + + Rake::Task["#{$root}/gen/proc_ctp/html/#{args[:model_name]}-CTP.html"].invoke + end +end diff --git a/backends/proc_ctp/templates/proc_ctp.adoc.erb b/backends/proc_ctp/templates/proc_ctp.adoc.erb new file mode 100644 index 000000000..90f8b2793 --- /dev/null +++ b/backends/proc_ctp/templates/proc_ctp.adoc.erb @@ -0,0 +1,189 @@ +<%= portfolio_design.include_erb("beginning.adoc.erb") %> + +// Book title. Need "":doctype: book" immediately after book title (no blank lines allowed). += <%= proc_cert_model.name %> Processor CTP (Certification Test Plan) +:doctype: book + +[preface] += CTP Book Structure + +A CTP book is composed of 3 book parts as follows: + +* <> is the core CTP documentation including an appendix each for extension, instruction, and CSR + that could be present in a certificate. +* <> is the RISC-V Unprivileged ISA manual, volume I (uses release specified by the certificate) +* <> is the RISC-V Privileged ISA manual, volume II (uses release specified by the certificate) + +// Part 1 title +[#udb:doc] += <%= proc_cert_model.name %> Processor Certification Test Plan: Part 1 + +[Preface] +== CTP Revision History + +<%= proc_cert_design.include_erb("rev_history.adoc.erb") %> + +[Preface] +== Typographic Conventions + +<%= proc_cert_design.include_erb("typographic.adoc.erb") %> + +[Preface] +== Glossary + +<%= proc_cert_design.include_erb("glossary.adoc.erb") %> + +== Introduction + +<%= proc_cert_model.introduction %> + +<%= proc_cert_class.introduction %> + +=== What's a CTP? + +Certification Test Plans (CTPs) list certification coverage points and how they will be tested via +certification test procedures (step by step descriptions of tests). +CTPs are developed by the RVI CSC (Certification Steering Committee) organization in collaboration +with the RVI TSC (Technical Steering Committee) organization who creates RISC-V standards including +the RISC-V Unprivileged and Privileged ISA manuals. + +Each certificate has a corresponding CRD and CTP: + +* The CRD defines the certification requirements an implementation must meet to obtain certification. +* The CTP defines the certification coverage points and certification test procedures followed by the certification + tests that an implementation must pass to obtain certification. + +The certification coverage points reference text in any or all of the following: + +* RISC-V ISA manuals in parts 2 and 3 +* Community-generated documentation located in part 1 (https://github.com/riscv-software-src/riscv-unified-db) +* IDL (ISA Description Language) executable psuedo-code descriptions located in part 1 + +The RISC-V ISA manuals are the preferred reference for certification coverage points since these manuals +represent the ratified RISC-V standards. +However, if the information in the ISA manuals isn't sufficiently clear or complete for certification purposes +the content in Part 1 is used. + +=== Naming Scheme + +==== CTP Naming + +<%= proc_cert_design.include_erb("rvcp_naming_scheme.adoc.erb") %> + +The specific rules for updating the version number for a CTP are as follows: + +* The release number is updated for changes that *could* cause a previously certified + implementation to now fail. An example is increasing coverage. +* The release number is updated when the CRD referenced by a CTP adds support for new optional behaviors. +* The release number is updated for documentation fixes/improvements. + These changes *cannot* cause a previously certified implementation to no longer pass certification. + +==== Processor Naming Scheme + +<%= proc_cert_design.include_erb("proc_naming_scheme.adoc.erb") %> + +<%= proc_cert_design.include_erb("related_specs.adoc.erb") %> +<%= proc_cert_design.include_erb("priv_modes.adoc.erb") %> + +== Coverage Points + +This section contains a view of the coverage point information organized by kind +(i.e., extension, instruction, or CSR). +This document is generated by a database backend so all views of the information are consistent. + +=== Extension Coverage Points + +<% proc_cert_model.in_scope_extensions.each do |ext| -%> +<%= portfolio_design.include_erb("coverage_points.adoc.erb", { "db_obj" => ext, "org" => "sep"}) %> +<% end -%> + +=== Instruction Coverage Points + +<% proc_cert_model.in_scope_instructions(design).each do |inst| -%> +<%= portfolio_design.include_erb("coverage_points.adoc.erb", { "db_obj" => inst, "org" => "sep"}) %> +<% end -%> + +=== CSR Coverage Points + +<% proc_cert_model.in_scope_csrs(design).each do |csr| -%> +<%= portfolio_design.include_erb("coverage_points.adoc.erb", { "db_obj" => csr, "org" => "sep" }) %> +<% csr.implemented_fields(design).each do |field| -%> +<%= portfolio_design.include_erb("coverage_points.adoc.erb", { "db_obj" => field, "org" => "sep" }) %> +<% end -%> +<% end -%> + +== Test Procedures + +This section contains just view of the test procedure information organized by kind +(i.e., extension, instruction, or CSR). +This document is generated by a database backend so all views of the information are consistent. + +=== Extension Test Procedures + +<% proc_cert_model.in_scope_extensions.each do |ext| -%> +<%= portfolio_design.include_erb("test_procedures.adoc.erb", { "db_obj" => ext, "org" => "sep" }) %> +<% end -%> + +=== Instruction Test Procedures + +<% proc_cert_model.in_scope_instructions(design).each do |inst| -%> +<%= portfolio_design.include_erb("test_procedures.adoc.erb", { "db_obj" => inst, "org" => "sep" }) %> +<% end -%> + +=== CSR Test Procedures + +<% proc_cert_model.in_scope_csrs(design).each do |csr| -%> +<%= portfolio_design.include_erb("test_procedures.adoc.erb", { "db_obj" => csr, "org" => "sep" }) %> +<% csr.implemented_fields(design).each do |field| -%> +<%= portfolio_design.include_erb("test_procedures.adoc.erb", { "db_obj" => field, "org" => "sep" }) %> +<% end -%> +<% end -%> + +== Combined Coverage Points & Test Procedures + +This section contains a combined view of the coverage point and test procedure information organized +by kind (i.e., extension, instruction, or CSR). +This document is generated by a database backend so all views of the information are consistent. + +=== Extension Coverage Points & Test Procedures + +<% proc_cert_model.in_scope_extensions.each do |ext| -%> +<%= portfolio_design.include_erb("coverage_points.adoc.erb", { "db_obj" => ext, "org" => "combo"}) %> +<%= portfolio_design.include_erb("test_procedures.adoc.erb", { "db_obj" => ext, "org" => "combo"}) %> +<% end -%> + +=== Instruction Coverage Points & Test Procedures + +<% proc_cert_model.in_scope_instructions(design).each do |inst| -%> +<%= portfolio_design.include_erb("coverage_points.adoc.erb", { "db_obj" => inst, "org" => "combo"}) %> +<%= portfolio_design.include_erb("test_procedures.adoc.erb", { "db_obj" => inst, "org" => "combo"}) %> +<% end -%> + +=== CSR Coverage Points & Test Procedures + +<% proc_cert_model.in_scope_csrs(design).each do |csr| -%> +<%= portfolio_design.include_erb("coverage_points.adoc.erb", { "db_obj" => csr, "org" => "combo"}) %> +<%= portfolio_design.include_erb("test_procedures.adoc.erb", { "db_obj" => csr, "org" => "combo"}) %> +<% csr.implemented_fields(design).each do |field| -%> +<%= portfolio_design.include_erb("coverage_points.adoc.erb", { "db_obj" => field, "use_description_list" => true, "org" => "combo"}) %> +<%= portfolio_design.include_erb("test_procedures.adoc.erb", { "db_obj" => field, "use_description_list" => true, "org" => "combo"}) %> +<% end -%> +<% end -%> + +// Appendices +<%= portfolio_design.include_erb("ext_appendix.adoc.erb", { "gen_ctp_content" => true }) %> +<%= portfolio_design.include_erb("inst_appendix.adoc.erb", { "gen_ctp_content" => true }) %> +<%= portfolio_design.include_erb("csr_appendix.adoc.erb", { "gen_ctp_content" => true }) %> +<%= portfolio_design.include_erb("idl_func_appendix.adoc.erb") %> + +<<< +<% puts "UPDATE: Including riscv-unprivileged.adoc" -%> +// Reset chapter numbering +:!chapter-number: +include::ext/riscv-isa-manual/src/riscv-unprivileged.adoc[] + +<<< +<% puts "UPDATE: Including riscv-privileged.adoc" -%> +// Reset chapter numbering +:!chapter-number: +include::ext/riscv-isa-manual/src/riscv-privileged.adoc[] diff --git a/backends/profile/tasks.rake b/backends/profile/tasks.rake new file mode 100644 index 000000000..7cd49fc63 --- /dev/null +++ b/backends/profile/tasks.rake @@ -0,0 +1,124 @@ +# frozen_string_literal: true +# +# Contains Rake rules to generate adoc, PDF, and HTML for a profile release. + +require "pathname" + +PROFILE_DOC_DIR = Pathname.new "#{$root}/backends/profile" + +Dir.glob("#{$root}/arch/profile_release/*.yaml") do |f| + release_name = File.basename(f, ".yaml") + release_obj = YAML.load_file(f, permitted_classes: [Date]) + raise "Can't parse #{f}" if release_obj.nil? + + raise "Ill-formed profile release file #{f}: missing 'class' field" if release_obj['class'].nil? + class_name = File.basename(release_obj['class']['$ref'].split("#")[0], ".yaml") + raise "Ill-formed profile release file #{f}: can't parse class name" if class_name.nil? + + raise "Ill-formed profile release file #{f}: missing 'profiles' field" if release_obj['profiles'].nil? + profile_names = release_obj['profiles'].map {|p| File.basename(p['$ref'].split("#")[0], ".yaml") } + raise "Ill-formed profile release file #{f}: can't parse profile names" if profile_names.nil? + + profile_pathnames = profile_names.map {|profile_name| "#{$root}/arch/profile/#{profile_name}.yaml" } + + file "#{$root}/gen/profile/adoc/#{release_name}ProfileRelease.adoc" => [ + __FILE__, + "#{$root}/arch/profile_class/#{class_name}.yaml", + "#{$root}/arch/profile_release/#{release_name}.yaml", + "#{$root}/lib/arch_obj_models/profile.rb", + "#{$root}/lib/arch_obj_models/portfolio.rb", + "#{$root}/lib/portfolio_design.rb", + "#{$root}/lib/design.rb", + "#{$root}/lib/backend_helpers.rb", + "#{$root}/backends/portfolio/templates/ext_appendix.adoc.erb", + "#{$root}/backends/portfolio/templates/inst_appendix.adoc.erb", + "#{$root}/backends/portfolio/templates/csr_appendix.adoc.erb", + "#{$root}/backends/portfolio/templates/beginning.adoc.erb", + "#{PROFILE_DOC_DIR}/templates/profile.adoc.erb" + ].concat(profile_pathnames) do |t| + arch = pf_create_arch + + # Create PortfolioRelease for specific portfolio release as specified in its arch YAML file. + # The Architecture object also creates all other portfolio-related class instances from their arch YAML files. + # None of these objects are provided with a Design object when created. + puts "UPDATE: Creating ProfileRelease object for #{release_name}" + profile_release = arch.profile_release(release_name) + profile_class = profile_release.profile_class + + # Create the one PortfolioDesign object required for the ERB evaluation. + # Provide it with all the profiles in this ProfileRelease. + puts "UPDATE: Creating PortfolioDesign object using profile release #{release_name}" + portfolio_design = + PortfolioDesign.new(release_name, arch, PortfolioDesign.profile_release_type, profile_release.profiles, profile_class) + + # Create empty binding and then specify explicitly which variables the ERB template can access. + # Seems to use this method name in stack backtraces (hence its name). + def evaluate_erb + binding + end + erb_binding = evaluate_erb + portfolio_design.init_erb_binding(erb_binding) + erb_binding.local_variable_set(:profile_release, profile_release) + erb_binding.local_variable_set(:profile_class, profile_class) + + pf_create_adoc("#{PROFILE_DOC_DIR}/templates/profile.adoc.erb", erb_binding, t.name, portfolio_design) + end + + file "#{$root}/gen/profile/pdf/#{release_name}ProfileRelease.pdf" => [ + __FILE__, + "#{$root}/gen/profile/adoc/#{release_name}ProfileRelease.adoc" + ] do |t| + pf_adoc2pdf("#{$root}/gen/profile/adoc/#{release_name}ProfileRelease.adoc", t.name) + end + + file "#{$root}/gen/profile/html/#{release_name}ProfileRelease.html" => [ + __FILE__, + "#{$root}/gen/profile/adoc/#{release_name}ProfileRelease.adoc" + ] do |t| + pf_adoc2html("#{$root}/gen/profile/adoc/#{release_name}ProfileRelease.adoc", t.name) + end +end + +namespace :gen do + desc <<~DESC + Generate profile documentation for a profile release as a PDF. + + Required options: + release_name - The name of the profile release under arch/profile_release + DESC + task :profile_release_pdf, [:release_name] do |_t, args| + release_name = args[:release_name] + if release_name.nil? + warn "Missing required option: 'release_name'" + exit 1 + end + + unless File.exist?("#{$root}/arch/profile_release/#{release_name}.yaml") + warn "No profile release named '#{release_name}' found in arch/profile_release" + exit 1 + end + + Rake::Task["#{$root}/gen/profile/pdf/#{release_name}ProfileRelease.pdf"].invoke + end + + desc <<~DESC + Generate profile documentation for a profile release as an HTML. + + Required options: + release_name - The name of the profile release under arch/profile_release + DESC + task :profile_release_html, [:release_name] do |_t, args| + release_name = args[:release_name] + if release_name.nil? + warn "Missing required option: 'release_name'" + exit 1 + end + + unless File.exist?("#{$root}/arch/profile_release/#{release_name}.yaml") + warn "No profile release named '#{release_name}' found in arch/profile_release" + exit 1 + end + + Rake::Task["#{$root}/gen/profile/html/#{release_name}ProfileRelease.html"].invoke + end +end diff --git a/backends/profile_doc/templates/profile.adoc.erb b/backends/profile/templates/profile.adoc.erb similarity index 63% rename from backends/profile_doc/templates/profile.adoc.erb rename to backends/profile/templates/profile.adoc.erb index beed6be90..ef03127f1 100644 --- a/backends/profile_doc/templates/profile.adoc.erb +++ b/backends/profile/templates/profile.adoc.erb @@ -1,58 +1,9 @@ -[[header]] -:description: <%= profile_release.marketing_name %> Profile -:revdate: <%= profile_release.ratification_date.nil? ? Date.today : profile_release.ratification_date %> +<%= portfolio_design.include_erb("beginning.adoc.erb") %> -// TODO - Figure out what we really want here - Change percent hash to percent equals. -// :revnumber: <%# profile_release.map(&:version).sort.last %> - -:revmark: "TODO: revmark" -:company: <%= profile_class.company.name %> -:url-riscv: https://riscv.org -:doctype: book -:preface-title: Licensing and Acknowledgements -:colophon: -:appendix-caption: Appendix -:title-logo-image: image:risc-v_logo.png["RISC-V International Logo",pdfwidth=3.25in,align=center] +:revdate: <%= profile_release.ratification_date.nil? ? Date.today : profile_release.ratification_date %> <%# unless profile_release.state == "ratified" -%> :page-background-image: image:draft.png[opacity=20%] <%# end -%> -:back-cover-image: image:riscv-horizontal-color.svg[opacity=25%] -// Settings -:experimental: -:reproducible: -:wavedrom: <%= $root %>/node_modules/.bin/wavedrom-cli -// TODO: needs to be changed -:imagesoutdir: images -:icons: font -:lang: en -:example-caption: Example -:listing-caption: Listing -:table-caption: Table -:figure-caption: Figure -:xrefstyle: short -:chapter-refsig: Chapter -:section-refsig: Section -:appendix-refsig: Appendix -:sectnums: -:toc: left -:toclevels: 5 -:source-highlighter: pygments -ifdef::backend-pdf[] -:source-highlighter: rouge -endif::[] -:data-uri: -:hide-uri-scheme: -:stem: -:footnote: -:stem: latexmath -:footnote: -:le: ≤ -:ge: ≥ -:ne: ≠ -:approx: ≈ -:inf: ∞ -:csrname: envcfg -:imagesdir: images = <%= profile_release.name %> Profile Release @@ -225,7 +176,7 @@ A profile may specify that certain conditions will cause a requested trap (such as an `ecall` made in the highest-supported privilege mode) or fatal trap to the enclosing execution environment. The profile does not specify the behavior of the enclosing execution environment -in handling requested or fatal traps. +iusually n handling requested or fatal traps. NOTE: In particular, a profile does not specify the set of ECALLs available in the outer execution environment. This should be @@ -312,10 +263,6 @@ 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. -// XXX - Need to create render() Ruby function. -// See https://github.com/riscv-software-src/riscv-unified-db/issues/59 -// <%# render("#{$root}/backends/portfolio_doc/templates/family_intro.erb", portfolio_class: profile_class) %> - == <%= profile_class.name %> Profile Class <%= profile_class.introduction %> @@ -351,7 +298,7 @@ Ratification date:: <%= profile_release.ratification_date %> <%= profile_release.introduction %> -<%= profile_release.marketing_name %> has <%= profile_release.referenced_extensions.reduce(0) { |sum, ext| sum + ext.params.size } %> +<%= profile_release.marketing_name %> has <%= profile_release.in_scope_extensions.reduce(0) { |sum, ext| sum + ext.params.size } %> associated implementation-defined parameters across all its defined profiles. <% unless profile_release.description.nil? -%> @@ -384,10 +331,10 @@ Recommendations are not strictly mandated but are included to guide implementers ==== Implementation-dependencies -<%= profile.marketing_name %> has <%= profile.referenced_extensions.reduce(0) { |sum, ext| sum + ext.params.size } %> +<%= profile.marketing_name %> has <%= profile.in_scope_extensions.reduce(0) { |sum, ext| sum + ext.params.size } %> associated implementation-defined parameters. -<% profile.referenced_extensions.each do |ext| -%> +<% profile.in_scope_extensions.each do |ext| -%> <% ext.params.sort_by { |p| p.name }.each do |param| -%> <%= param.name %>:: + @@ -406,13 +353,13 @@ associated implementation-defined parameters. === <%= profile_class.processor_kind %> Profile Releases The <%= profile_class.processor_kind %> processor kind has <%= profile_class.profile_releases_matching_processor_kind.size %> processor -profile releases that reference a total of <%= profile_class.referenced_extensions_matching_processor_kind.size %> extensions. +profile releases that reference a total of <%= profile_class.in_scope_extensions_matching_processor_kind.size %> extensions. .Extension Presence |=== | Extension | <%= profile_class.profile_releases_matching_processor_kind.map(&:marketing_name).join(" | ") %> -<% profile_class.referenced_extensions_matching_processor_kind.sort_by(&:name).each do |ext| -%> +<% profile_class.in_scope_extensions_matching_processor_kind.sort_by(&:name).each do |ext| -%> | <%= ext.name %> | <%= profile_class.profile_releases_matching_processor_kind.map { |profile_release| profile_release.extension_presence(ext.name) }.join(" | ") %> <% end -%> |=== @@ -420,13 +367,13 @@ profile releases that reference a total of <%= profile_class.referenced_extensio === <%= profile_class.marketing_name %> Profile Releases The <%= profile_class.marketing_name %> Profile Class has <%= profile_class.profile_releases.size %> releases that -reference a total of <%= profile_class.referenced_extensions.size %> extensions. +reference a total of <%= profile_class.in_scope_extensions.size %> extensions. .Extension Presence |=== | Extension | <%= profile_class.profile_releases.map(&:marketing_name).join(" | ") %> -<% profile_class.referenced_extensions.sort_by(&:name).each do |ext| -%> +<% profile_class.in_scope_extensions.each do |ext| -%> | <%= ext.name %> | <%= profile_class.profile_releases.map { |profile_release| profile_release.extension_presence(ext.name) }.join(" | ") %> <% end -%> |=== @@ -434,7 +381,7 @@ reference a total of <%= profile_class.referenced_extensions.size %> extensions. === <%= profile_release.marketing_name %> Profiles The <%= profile_release.marketing_name %> Profile Release has <%= profile_release.profiles.size %> profiles that -reference a total of <%= profile_release.referenced_extensions.size %> extensions. +reference a total of <%= profile_release.in_scope_extensions.size %> extensions. [NOTE] Extensions present in a profile are also present in higher-privileged profiles in the same profile release. @@ -443,300 +390,12 @@ Extensions present in a profile are also present in higher-privileged profiles i |=== | Extension | <%= profile_release.profiles.map(&:marketing_name).join(" | ") %> -<% profile_release.referenced_extensions.sort_by(&:name).each do |ext| -%> +<% profile_release.in_scope_extensions.each do |ext| -%> | <%= ext.name %> | <%= profile_release.profiles.map { |profile| profile.extension_presence(ext.name) }.join(" | ") %> <% end -%> |=== -<<< -[appendix] -== Extension Details - -<% profile_release.referenced_extensions.sort.each do |ext| -%> -<<< -=== <%= ext.name %> Extension -<%= ext.long_name %> - -.<%= ext.name %> Extension Presence -|=== -| Profile | v<%= ext.versions.map { |ext_ver| ext_ver.canonical_version.to_s }.join(" | v") %> - -<% profile_release.profiles.each do |profile| -%> -| <%= profile.marketing_name %> | <%= profile.version_greatest_presence(ext.name, ext.versions).join(" | ") -%> -<% end -%> - -|=== - -<% ext.versions.each do |v| -%> -<%= v.canonical_version %>:: - State::: - <%= v.state %> - <% if v.state == "ratified" && !v.ratification_date.nil? -%> - Ratification date::: - <%= v.ratification_date %> - <% end # if %> - <% unless v.changes.empty? -%> - Changes::: - - <% v.changes.each do |c| -%> - * <%= c %> - <% end -%> - - <% end -%> - <% unless v.url.nil? -%> - Ratification document::: - <%= v.url %> - <% end -%> - <% unless v.implications -%> - Implies::: - <% v.implications.each do |i| -%> - * `<%= i.name %>` version <%= i.version %> - <% end -%> - <% end -%> -<% end -%> - -==== Synopsis - -:leveloffset: +3 - -<%= ext.description %> - -:leveloffset: -3 - -// TODO: GitHub issue 92: Use version specified by each profile and add version info to inst table below. -<%- insts = cfg_arch.instructions.select { |i| i.defined_by?(ext.min_version) } -%> -<%- unless insts.empty? -%> -==== Instructions - -The following instructions are added by this extension: - -[cols="1,3"] -|=== -<% insts.sort.each do |inst| -%> - | <%= "`#{inst.name}`" %> | *<%= inst.long_name %>* -<% end -%> -|=== -<% end -%> - -<% unless ext.params.empty? -%> -==== Parameters - -This extension has the following implementation options: - -<% ext.params.sort_by { |p| p.name }.each do |param| -%> -<%= param.name %>:: -+ --- -<%= param.desc %> --- -<% end -%> - -<% end -%> -<% end -%> - -<<< -[appendix] -== Instruction Details - -<%= - insts = profile_release.referenced_extensions.map { |ext| ext.instructions }.flatten.uniq - insts.sort_by!(&:name) --%> - -<% insts.each do |inst| -%> -<<< -[[inst-<%=inst.name.gsub('.', '_')%>-def]] -=== <%= inst.name %> - -*<%= inst.long_name %>* - -This instruction is defined by: - -<%= inst.defined_by_condition.to_asciidoc %> - -==== Encoding - -<% if inst.multi_encoding? -%> -[NOTE] -This instruction has different encodings in RV32 and RV64. - -[tabs] -==== -RV32:: -+ -[wavedrom, ,svg,subs='attributes',width="100%"] -.... -<%= JSON.dump inst.wavedrom_desc(32) %> -.... - -RV64:: -+ -[wavedrom, ,svg,subs='attributes',width="100%"] -.... -<%= JSON.dump inst.wavedrom_desc(64) %> -.... -==== -<% else -%> -[wavedrom, ,svg,subs='attributes',width="100%"] -.... -<%= JSON.dump inst.wavedrom_desc(inst.base.nil? ? 32 : inst.base) %> -.... -<% end -%> - -==== Synopsis - -<%= inst.description %> - -==== Access -<% if profile_release.referenced_extensions.any? { |e| e.name == "H" } -%> -[cols="^,^,^,^,^"] -<% else -%> -[cols="^,^,^"] -<% end -%> -|=== -| M | <% if profile_release.referenced_extensions.any? { |e| e.name == "H" } -%>HS<% else -%>S<% end -%> | U <% if profile_release.referenced_extensions.any? { |e| e.name == "H" } -%> | VS | VU <% end -%> - -| [.access-always]#Always# -| [.access-<%=inst.access['s']%>]#<%= inst.access['s'].capitalize %># -| [.access-<%=inst.access['u']%>]#<%= inst.access['u'].capitalize %># -<% if profile_release.referenced_extensions.any? { |e| e.name == "H" } %> -| [.access-<%=inst.access['vs']%>]#<%= inst.access['vs'].capitalize %># -| [.access-<%=inst.access['vu']%>]#<%= inst.access['vu'].capitalize %># -<% end %> -|=== - -<% if inst.access_detail? -%> -<%= inst.access_detail %> -<% end -%> - - -==== Decode Variables - -<% if inst.multi_encoding? -%> -[tabs] -==== -RV32:: -+ -[source.idl] ----- -<% inst.decode_variables(32).each do |d| -%> -<%= d.sext? ? 'signed ' : '' %>Bits<<%= d.size %>> <%= d.name %> = <%= d.extract %>; -<% end -%> ----- - -RV64:: -+ -[source,idl] ----- -<% inst.decode_variables(64).each do |d| -%> -<%= d.sext? ? 'signed ' : '' %>Bits<<%= d.size %>> <%= d.name %> = <%= d.extract %>; -<% end -%> ----- -==== -<% else -%> -[source,idl] ----- -<% inst.decode_variables(inst.base.nil? ? 32 : inst.base).each do |d| -%> -<%= d.sext? ? 'signed ' : '' %>Bits<<%= d.size %>> <%= d.name %> = <%= d.extract %>; -<% end -%> ----- -<% end -%> - -==== Execution - -<% xlens = inst.base.nil? ? [32, 64] : [inst.base] -%> - -<% if inst.key?("operation()") -%> -[source,idl,subs="specialchars,macros"] ----- -<%= inst.operation_ast(cfg_arch.symtab).gen_adoc %> ----- -<% end -%> - -==== Exceptions - -<% exception_list = inst.reachable_exceptions_str(cfg_arch.symtab) -%> -<% if exception_list.empty? -%> -This instruction does not generate synchronous exceptions. -<% else -%> -This instruction may result in the following synchronous exceptions: - - <% exception_list.sort.each do |etype| -%> - * <%= etype %> - <% end -%> - -<% end -%> - -<% end -%> - -<<< -[appendix] -== CSR Details - -<% - csrs = profile_release.referenced_extensions.map { |ext| ext.csrs }.flatten.uniq - csrs.sort_by!(&:name) --%> - -<% csrs.each do |csr| -%> -<<< -[[csr-<%= csr.name %>-def]] -=== <%= csr.name %> - -*<%= csr.long_name %>* - -<% unless csr.base.nil? -%> -[NOTE] --- -`<%= csr.name %>` is only defined in RV<%= csr.base %>. --- -<% end -%> - -<%= csr.description %> - -==== Attributes -[%autowidth] -|=== -h| CSR Address | <%= "0x#{csr.address.to_s(16)}" %> -<% if csr.priv_mode == 'VS' -%> -h| Virtual CSR Address | <%= "0x#{csr.virtual_address.to_s(16)}" %> -<% end -%> -h| Defining extension a| <%= csr.defined_by_condition.to_asciidoc %> -<% if csr.dynamic_length?(cfg_arch) -%> -h| Length | <%= csr.length_pretty(cfg_arch) %> -<% else -%> -h| Length | <%= csr.length_pretty(cfg_arch) %> -<% end -%> -h| Privilege Mode | <%= csr.priv_mode %> -|=== - - -==== Format -<% unless csr.dynamic_length?(cfg_arch) || csr.fields.any? { |f| f.dynamic_location?(cfg_arch) } -%> -<%# CSR has a known static length, so there is only one format to display -%> -.<%= csr.name %> format -[wavedrom, ,svg,subs='attributes',width="100%"] -.... -<%= JSON.dump csr.wavedrom_desc(cfg_arch, csr.base.nil? ? 32 : csr.base) %> -.... -<% else -%> -<%# CSR has a dynamic length, or a field has a dynamic location, - so there is more than one format to display -%> -This CSR format changes dynamically with XLEN. - -.<%= csr.name %> Format when <%= csr.length_cond32 %> -[wavedrom, ,svg,subs='attributes',width="100%"] -.... -<%= JSON.dump csr.wavedrom_desc(cfg_arch, 32) %> -.... - -.<%= csr.name %> Format when <%= csr.length_cond64 %> -[wavedrom, ,svg,subs='attributes',width="100%"] -.... -<%= JSON.dump csr.wavedrom_desc(cfg_arch, 64) %> -.... - - -<% end -%> - -<% end -%> +<%= portfolio_design.include_erb("ext_appendix.adoc.erb") %> +<%= portfolio_design.include_erb("inst_appendix.adoc.erb") %> +<%= portfolio_design.include_erb("csr_appendix.adoc.erb") %> +<%= portfolio_design.include_erb("idl_func_appendix.adoc.erb") %> diff --git a/backends/profile_doc/tasks.rake b/backends/profile_doc/tasks.rake deleted file mode 100644 index b18fc3993..000000000 --- a/backends/profile_doc/tasks.rake +++ /dev/null @@ -1,110 +0,0 @@ -# frozen_string_literal: true - -rule %r{#{$root}/gen/profile_doc/adoc/.*\.adoc} => [ - __FILE__, - "#{$root}/lib/arch_obj_models/profile.rb", - "#{$root}/backends/profile_doc/templates/profile.adoc.erb", - Dir.glob("#{$root}/arch/profile_release/**/*.yaml") -].flatten do |t| - profile_release_name = Pathname.new(t.name).basename(".adoc").to_s - profile_release = cfg_arch_for("_").profile_release(profile_release_name) - raise ArgumentError, "No profile release named '#{profile_release_name}'" if profile_release.nil? - - 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 - - # Switch to the generated profile certificate cfg arch and set some variables available to ERB template. - cfg_arch = cfg_arch_for("_") - - # Create empty binding and then specify explicitly which variables the ERB template can access. - def create_empty_binding - binding - end - erb_binding = create_empty_binding - erb_binding.local_variable_set(:cfg_arch, cfg_arch) - erb_binding.local_variable_set(:profile_class, profile_release.profile_class) - erb_binding.local_variable_set(:profile_release, profile_release) - erb_binding.local_variable_set(:portfolio_class, profile_release.profile_class) - - FileUtils.mkdir_p File.dirname(t.name) - File.write t.name, AsciidocUtils.resolve_links(cfg_arch.find_replace_links(erb.result(erb_binding))) - puts "Generated adoc source at #{t.name}" -end - -rule %r{#{$root}/gen/profile_doc/pdf/.*\.pdf} => proc { |tname| - profile_release_name = Pathname.new(tname).basename(".pdf") - [__FILE__, "#{$root}/gen/profile_doc/adoc/#{profile_release_name}.adoc"] -} do |t| - profile_release_name = Pathname.new(t.name).basename(".pdf") - - adoc_filename = "#{$root}/gen/profile_doc/adoc/#{profile_release_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_filename - ].join(" ") - - puts - puts "SUCCESS! File written to #{t.name}" -end - -rule %r{#{$root}/gen/profile_doc/html/.*\.html} => proc { |tname| - profile_release_name = Pathname.new(tname).basename(".html") - [__FILE__, "#{$root}/gen/profile_doc/adoc/#{profile_release_name}.adoc"] -} do |t| - profile_release_name = Pathname.new(t.name).basename(".html") - - adoc_filename = "#{$root}/gen/profile_doc/adoc/#{profile_release_name}.adoc" - - FileUtils.mkdir_p File.dirname(t.name) - sh [ - "asciidoctor", - "-w", - "-v", - "-a toc", - "-a imagesdir=#{$root}/ext/docs-resources/images", - "-r asciidoctor-diagram", - "-r #{$root}/backends/ext_pdf_doc/idl_lexer", - "-o #{t.name}", - adoc_filename - ].join(" ") - - puts - puts "SUCCESS! File written to #{t.name}" -end - -namespace :gen do - desc "Create a specification PDF for +profile_release+" - task :profile, [:profile_release] do |_t, args| - profile_release_name = args[:profile_release] - raise ArgumentError, "Missing required option +profile_release+" if profile_release_name.nil? - - profile_release = cfg_arch_for("_").profile_release(profile_release_name) - raise ArgumentError, "No profile release named '#{profile_release_name}'" if profile_release.nil? - - Rake::Task["#{$root}/gen/profile_doc/pdf/#{profile_release_name}.pdf"].invoke - end - - desc "Create a specification HTML for +profile_release+" - task :profile_html, [:profile_release] do |_t, args| - profile_release_name = args[:profile_release] - raise ArgumentError, "Missing required option +profile_release+" if profile_release_name.nil? - - profile_release = cfg_arch_for("_").profile_release(profile_release_name) - raise ArgumentError, "No profile release named '#{profile_release_name}" if profile_release.nil? - - Rake::Task["#{$root}/gen/profile_doc/html/#{profile_release_name}.html"].invoke - end -end diff --git a/backends/templates/README.adoc b/backends/templates/README.adoc new file mode 100644 index 000000000..9cf268e10 --- /dev/null +++ b/backends/templates/README.adoc @@ -0,0 +1 @@ +This directory contains partial ERB templates shared by multiple backends. diff --git a/backends/common_templates/adoc/csr.adoc.erb b/backends/templates/csr.adoc.erb similarity index 74% rename from backends/common_templates/adoc/csr.adoc.erb rename to backends/templates/csr.adoc.erb index 0f9187ded..9394d6b1d 100644 --- a/backends/common_templates/adoc/csr.adoc.erb +++ b/backends/templates/csr.adoc.erb @@ -1,4 +1,4 @@ -<%= anchor_for_csr(csr.name) %> +<%= anchor_for_udb_doc_csr(csr.name) %> = <%= csr.name %> *<%= csr.long_name %>* @@ -13,17 +13,17 @@ h| CSR Address | <%= "0x#{csr.address.to_s(16)}" %> <%- if csr.priv_mode == 'VS' -%> h| Virtual CSR Address | <%= "0x#{csr.virtual_address.to_s(16)}" %> <%- end -%> -h| Length | <%= csr.length_pretty(cfg_arch) %> +h| Length | <%= csr.length_pretty(design) %> h| Privilege Mode | <%= csr.priv_mode %> |=== == Format -<%- unless csr.dynamic_length?(cfg_arch) || csr.fields.any? { |f| f.dynamic_location?(cfg_arch) } -%> +<%- unless csr.dynamic_length?(design) || csr.fields.any? { |f| f.dynamic_location?(design) } -%> <%# CSR has a known static length, so there is only one format to display -%> .<%= csr.name %> format [wavedrom, ,svg,subs='attributes',width="100%"] .... -<%= JSON.dump csr.wavedrom_desc(cfg_arch, 64) %> +<%= JSON.dump csr.wavedrom_desc(design, 64) %> .... <%- else -%> <%# CSR has a dynamic length, or a field has a dynamic location, @@ -33,13 +33,13 @@ This CSR format changes dynamically. .<%= csr.name %> Format when <%= csr.length_cond32 %> [wavedrom, ,svg,subs='attributes',width="100%"] .... -<%= JSON.dump csr.wavedrom_desc(cfg_arch, 32) %> +<%= JSON.dump csr.wavedrom_desc(design, 32) %> .... .<%= csr.name %> Format when <%= csr.length_cond64 %> [wavedrom, ,svg,subs='attributes',width="100%"] .... -<%= JSON.dump csr.wavedrom_desc(cfg_arch, 64) %> +<%= JSON.dump csr.wavedrom_desc(design, 64) %> .... <%- end -%> @@ -50,10 +50,10 @@ This CSR format changes dynamically. @Name @ Location @ Type @ Reset Value <%- csr.fields.each do |field| -%> -@ <%= link_to_csr_field(csr.name, field.name) %> -@ <%= field.location_pretty(cfg_arch) %> -@ <%= field.type_pretty(cfg_arch.symtab) %> -@ <%= field.reset_value_pretty(cfg_arch) %> +@ <%= link_to_udb_doc_csr_field(csr.name, field.name) %> +@ <%= field.location_pretty(design) %> +@ <%= field.type_pretty(design.symtab) %> +@ <%= field.reset_value_pretty(design) %> <%- end -%> |=== @@ -66,22 +66,22 @@ This CSR has no fields. However, it must still exist (not cause an `Illegal Inst <%- else -%> <%- csr.fields.each do |field| -%> -[[<%=csr.name%>-<%=field.name%>-def]] -=== `<%= field.name %>` +<%= anchor_for_udb_doc_csr_field(csr.name, field.name) %> +=== `<%= field.name %>` Field [example] **** Location:: -<%= field.location_pretty(cfg_arch) %> +<%= field.location_pretty(design) %> Description:: <%= field.description %> Type:: -<%= field.type_pretty(cfg_arch.symtab) %> +<%= field.type_pretty(design.symtab) %> Reset value:: -<%= field.reset_value_pretty(cfg_arch) %> +<%= field.reset_value_pretty(design) %> **** @@ -115,6 +115,6 @@ This CSR may return a value that is different from what is stored in hardware. [source,idl,subs="specialchars,macros"] ---- -<%= csr.sw_read_ast(cfg_arch.symtab).gen_adoc %> +<%= csr.sw_read_ast(design.symtab).gen_adoc %> ---- <%- end -%> diff --git a/backends/common_templates/adoc/inst.adoc.erb b/backends/templates/inst.adoc.erb similarity index 95% rename from backends/common_templates/adoc/inst.adoc.erb rename to backends/templates/inst.adoc.erb index 53f5d2e47..8ad54b987 100644 --- a/backends/common_templates/adoc/inst.adoc.erb +++ b/backends/templates/inst.adoc.erb @@ -1,4 +1,4 @@ -<%= anchor_for_inst(inst.name) %> +<%= anchor_for_udb_doc_inst(inst.name) %> = <%= inst.name %> Synopsis:: @@ -75,7 +75,7 @@ Operation:: <%- unless inst.data["operation()"].nil? -%> [source,idl,subs="specialchars,macros"] ---- -<%= inst.operation_ast(cfg_arch.symtab).gen_adoc %> +<%= inst.operation_ast(design.symtab).gen_adoc %> ---- <%- end -%> diff --git a/cert_flow.txt b/cert_flow.txt deleted file mode 100644 index 20d4be77a..000000000 --- a/cert_flow.txt +++ /dev/null @@ -1,82 +0,0 @@ -backends/certificate_doc/tasks.rake -bootstrap_cfg_arch = cfg_arch_for("rv#{base}") # rv32 or rv64 - Rakefile - Calls ConfiguredArchitecture.new("gen/resolved_arch/rv32") - Calls Architecture.new # Parent class - Calls Config.create("cfgs/rv32/cfg.yaml") # Factory class method - Loads config yaml file into @data # File specifies if fully, partially, or unconfigured - Calls PartialConfig.new(cfg path, @data) # Uses Ruby send() method - @mxlen = @data["params"].xlen - @name = "rv32" -bootstrap_cert_model = bootstrap_cfg_arch.cert_model(cert_model_name = "MC100-32") - Calls self.generate_obj_methods("cert_model", "certificate_model", CertModel) in Architecture # Magic - Calls define_method("cert_model") - Calls define_method("cert_models") - Creates @cert_models array and @cert_model_hash # Per arch_dir - For every certificiate model in the database - Loads yaml under gen/resolved_arch/rv32 into @cert_models hash - Calls cert_model.new() -> DatabaseObject.initialize(yaml, yaml_path, arch=base_cfg_arch) - @data = yaml - @arch = arch # rv32 -cfg_arch = bootstrap_cert_model.to_cfg_arch # In Portfolio class - Creates hash with mandatory extensions (with version requirement) and fully-constrained params (single_value?) - Uses in_scope_ext_reqs() and all_in_scope_ext_params() in Portfolio - Writes hash to yaml file in /tmp/.../MC100-32/cfg.yaml - Passes yaml file to ConfiguredArchitecture.new() - Creates Architecture (base class), Config, and PartialConfig again (see above) -cert_model = cfg_arch.cert_model(cert_model_name) - Creates CertModel for every model in the database and stores it in the real ConfiguredArchitecture object -Calls ERB template -Converts ERB result to ASCIIDOC - - -============================================================================================== -Actual rv32/cfg.yaml ---- -$schema: config_schema.json# -kind: architecture configuration -type: partially configured -name: rv32 -description: A generic RV32 system; only MXLEN is known -params: - XLEN: 32 -mandatory_extensions: - - name: "I" - version: ">= 0" - - name: "Sm" - version: ">= 0" -=============================================================================================== -Bootstrap /tmp/.../MC100-32/cfg.yaml ---- -"$schema": config_schema.json -type: partially configured -kind: architecture configuration -name: MC100-32 -description: A partially configured architecture definition corresponding to the MC100-32 - portfolio. -mandatory_extensions: -- name: I - version: - - "~> 2.1" -- name: C - version: - - "~> 2.2" -- name: M - version: - - "~> 2.0" -- name: Zicsr - version: - - "~> 2.0" -- name: Zicntr - version: - - "~> 2.0" -- name: Sm - version: - - "~> 1.11.0" -params: - MISALIGNED_SPLIT_STRATEGY: by_byte - PRECISE_SYNCHRONOUS_EXCEPTIONS: true - TRAP_ON_ECALL_FROM_M: true - TRAP_ON_EBREAK: true - M_MODE_ENDIANESS: little - XLEN: 32 diff --git a/docs/ruby/Instruction.html b/docs/ruby/Instruction.html index c343c424e..ab0ac71c7 100644 --- a/docs/ruby/Instruction.html +++ b/docs/ruby/Instruction.html @@ -314,7 +314,7 @@

  • - #exists_in_cfg?(possible_xlens, extensions) ⇒ Boolean + #exists_in_design?(possible_xlens, extensions) ⇒ Boolean @@ -1310,9 +1310,9 @@

    -

    +

    - #exists_in_cfg?(possible_xlens, extensions) ⇒ Boolean + #exists_in_design?(possible_xlens, extensions) ⇒ Boolean @@ -1398,7 +1398,7 @@

    # File 'lib/arch_def.rb', line 1178
     
    -def exists_in_cfg?(possible_xlens, extensions)
    +def exists_in_design?(possible_xlens, extensions)
       (@data["base"].nil? || (possible_xlens.include?(@data["base"]))) &&
         extensions.any? { |e| defined_by?(e) } &&
         extensions.none? { |e| excluded_by?(e) }
    diff --git a/docs/ruby/method_list.html b/docs/ruby/method_list.html
    index 25dc25cdb..e63b227ee 100644
    --- a/docs/ruby/method_list.html
    +++ b/docs/ruby/method_list.html
    @@ -1142,7 +1142,7 @@ 

    Method List

  • diff --git a/ext/csc-riscv-isa-manual b/ext/csc-riscv-isa-manual new file mode 160000 index 000000000..cdb14d641 --- /dev/null +++ b/ext/csc-riscv-isa-manual @@ -0,0 +1 @@ +Subproject commit cdb14d64167d7f1634d3d91045db0c13a7924987 diff --git a/ext/docs-resources b/ext/docs-resources index a76dd1d39..62576cdd2 160000 --- a/ext/docs-resources +++ b/ext/docs-resources @@ -1 +1 @@ -Subproject commit a76dd1d390cba28e3b1ce86313af03ce9b69399d +Subproject commit 62576cdd285615473c279eb9acecade9e0fe0e1f diff --git a/lib/arch_obj_models/certificate.rb b/lib/arch_obj_models/certificate.rb index 68d8a47cf..db51ca58a 100644 --- a/lib/arch_obj_models/certificate.rb +++ b/lib/arch_obj_models/certificate.rb @@ -1,41 +1,30 @@ # Classes for certificates. -# Each certificate model is a member of a certificate class. +# Each processor certificate model is a member of a processor certificate class. require_relative "portfolio" ################### -# CertClass Class # +# ProcCertClass Class # ################### -# Holds information from certificate class YAML file. +# Holds information from processor certificate class YAML file. # The inherited "data" member is the database of extensions, instructions, CSRs, etc. -class CertClass < PortfolioClass +class ProcCertClass < PortfolioClass def mandatory_priv_modes = @data["mandatory_priv_modes"] end -################### -# CertModel Class # -################### +####################### +# ProcCertModel Class # +####################### -# Holds information about a certificate model YAML file. +# Holds information about a processor certificate model YAML file. # The inherited "data" member is the database of extensions, instructions, CSRs, etc. -class CertModel < Portfolio +class ProcCertModel < Portfolio # @param obj_yaml [Hash] Contains contents of Certificate Model yaml file (put in @data) # @param data_path [String] Path to yaml file - # @param cfg_arch [ConfiguredArchitecture] Architecture for a specific configuration - def initialize(obj_yaml, yaml_path, arch: nil) + # @param arch [Architecture] Database of RISC-V standards + def initialize(obj_yaml, yaml_path, arch) super # Calls parent class with the same args I got - - # TODO: XXX: Don't allow Architecture class. - # See https://github.com/riscv-software-src/riscv-unified-db/pull/371 - unless arch.is_a?(ConfiguredArchitecture) || arch.is_a?(Architecture) - raise ArgumentError, "For #{name} arch is a #{arch.class} but must be a ConfiguredArchitecture" - end - - # TODO: XXX: Add back in arch.name. - # See https://github.com/riscv-software-src/riscv-unified-db/pull/371 - #puts "UPDATE: Creating CertModel object for #{name} using cfg #{cfg_arch.name}" - puts "UPDATE: Creating CertModel object for #{name}" end def unpriv_isa_manual_revision = @data["unpriv_isa_manual_revision"] @@ -45,19 +34,19 @@ def debug_manual_revision = @data["debug_manual_revision"] def tsc_profile return nil if @data["tsc_profile"].nil? - profile = cfg_arch.profile(@data["tsc_profile"]) + profile = arch.profile(@data["tsc_profile"]) raise "No profile '#{@data["tsc_profile"]}'" if profile.nil? profile end - # @return [CertClass] The certification class that this model belongs to. - def cert_class - cert_class = @cfg_arch.ref(@data["class"]['$ref']) - raise "No certificate class named '#{@data["class"]}'" if cert_class.nil? + # @return [ProcCertClass] The certification class that this model belongs to. + def proc_cert_class + proc_cert_class = @arch.ref(@data["class"]['$ref']) + raise "No processor certificate class named '#{@data["class"]}'" if proc_cert_class.nil? - cert_class + proc_cert_class end ##################### @@ -66,9 +55,9 @@ def cert_class # Holds extra requirements not associated with extensions or their parameters. class Requirement - def initialize(data, cfg_arch) + def initialize(data, arch) @data = data - @cfg_arch = cfg_arch + @arch = arch end def name = @data["name"] @@ -100,9 +89,9 @@ def when_pretty # Holds a group of Requirement objects to provide a one-level group. # Can't nest RequirementGroup objects to make multi-level group. class RequirementGroup - def initialize(data, cfg_arch) + def initialize(data, arch) @data = data - @cfg_arch = cfg_arch + @arch = arch end def name = @data["name"] @@ -131,7 +120,7 @@ def requirements @requirements = [] @data["requirements"].each do |req| - @requirements << Requirement.new(req, @cfg_arch) + @requirements << Requirement.new(req, @arch) end @requirements end @@ -142,8 +131,165 @@ def requirement_groups @requirement_groups = [] @data["requirement_groups"]&.each do |req_group| - @requirement_groups << RequirementGroup.new(req_group, @cfg_arch) + @requirement_groups << RequirementGroup.new(req_group, @arch) end @requirement_groups end + + ################################### + # Routines using InScopeParameter # + ################################### + + # @return [Array] Sorted 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_params + return @all_in_scope_params unless @all_in_scope_params.nil? + + @all_in_scope_params = [] + + @data["extensions"].each do |ext_name, ext_data| + next if ext_name[0] == "$" + + # Find Extension object from database + ext = @arch.extension(ext_name) + if ext.nil? + raise "Cannot find extension named #{ext_name}" + end + + ext_data["parameters"]&.each do |param_name, param_data| + param = ext.params.find { |p| p.name == param_name } + raise "There is no param '#{param_name}' in extension '#{ext_name}" if param.nil? + + next unless ext.versions.any? do |ext_ver| + ver_req = ext_data["version"] || ">= #{ext.min_version.version_spec}" + ExtensionRequirement.new(ext_name, ver_req, @arch).satisfied_by?(ext_ver) && + param.defined_in_extension_version?(ext_ver) + end + + @all_in_scope_params << InScopeParameter.new(param, param_data["schema"], param_data["note"]) + end + end + @all_in_scope_params.sort! + end + + # @param [ExtensionRequirement] + # @return [Array] Sorted 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_params(ext_req) + raise ArgumentError, "Expecting ExtensionRequirement" unless ext_req.is_a?(ExtensionRequirement) + + params = [] # Local variable, no caching + + # Get extension information from portfolio YAML for passed in extension requirement. + ext_data = @data["extensions"][ext_req.name] + raise "Cannot find extension named #{ext_req.name}" if ext_data.nil? + + # Find Extension object from database + ext = @arch.extension(ext_req.name) + raise "Cannot find extension named #{ext_req.name}" if ext.nil? + + # Loop through an extension's parameter constraints (hash) from the certificate model. + # Note that "&" is the Ruby safe navigation operator (i.e., skip do loop if nil). + ext_data["parameters"]&.each do |param_name, param_data| + # Find Parameter object from database + param = ext.params.find { |p| p.name == param_name } + raise "There is no param '#{param_name}' in extension '#{ext_req.name}" if param.nil? + + next unless ext.versions.any? do |ext_ver| + ext_req.satisfied_by?(ext_ver) && param.defined_in_extension_version?(ext_ver) + end + + params << InScopeParameter.new(param, param_data["schema"], param_data["note"]) + end + + params.sort! + end + + # @return [Array] Sorted list of parameters out of scope across all in scope extensions + # (those listed as mandatory or optional in the certificate model). + 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 = @arch.extension(ext_req.name) + ext.params.each do |param| + next if all_in_scope_params.any? { |c| c.param.name == param.name } + + next unless ext.versions.any? do |ext_ver| + ext_req.satisfied_by?(ext_ver) && + param.defined_in_extension_version?(ext_ver) + end + + @all_out_of_scope_params << param + end + end + @all_out_of_scope_params.sort! + end + + # @param ext_name [String] Extension name + # @return [Array] Sorted list of parameters that are out of scope for named extension. + def out_of_scope_params(ext_name) + all_out_of_scope_params.select{ |param| param.exts.any? { |ext| ext.name == ext_name } }.sort + end + + # @param param [Parameter] + # @return [Array] Sorted list of all in-scope extensions that define this parameter + # in the database and the parameter is in-scope. + def all_in_scope_exts_with_param(param) + raise ArgumentError, "Expecting Parameter" unless param.is_a?(Parameter) + + exts = [] + + # Iterate through all the extensions in the architecture database that define this parameter. + param.exts.each do |ext| + found = false + + in_scope_extensions.each do |potential_ext| + if ext.name == potential_ext.name + found = true + next + end + end + + if found + # Only add extensions that exist in this certificate model. + exts << ext + end + end + + # Return intersection of extension names + exts.sort_by!(&:name) + end + + # @param param [Parameter] + # @return [Array] List of all in-scope extensions that define this parameter in the + # database but the parameter is out-of-scope. + def all_in_scope_exts_without_param(param) + raise ArgumentError, "Expecting Parameter" unless param.is_a?(Parameter) + + exts = [] # Local variable, no caching + + # Iterate through all the extensions in the architecture database that define this parameter. + param.exts.each do |ext| + found = false + + in_scope_extensions.each do |potential_ext| + if ext.name == potential_ext.name + found = true + next + end + end + + if found + # Only add extensions that are in-scope (i.e., exist in this certificate model). + exts << ext + end + end + + # Return intersection of extension names + exts.sort_by!(&:name) + end end diff --git a/lib/arch_obj_models/csr.rb b/lib/arch_obj_models/csr.rb index 38bbc99e5..0cc4b88fc 100644 --- a/lib/arch_obj_models/csr.rb +++ b/lib/arch_obj_models/csr.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require_relative "obj" +require_relative "database_obj" # CSR definition class Csr < DatabaseObject @@ -46,68 +46,68 @@ def defined_in_base64? = @data["base"].nil? || @data["base"] == 64 # @return [Boolean] true if this CSR is defined regardless of the effective XLEN def defined_in_all_bases? = @data["base"].nil? - # @param cfg_arch [ConfiguredArchitecture] A configuration + # @param design [Design] The design # @return [Boolean] Whether or not the format of this CSR changes when the effective XLEN changes in some mode - def format_changes_with_xlen?(cfg_arch) - dynamic_length?(cfg_arch) || - implemented_fields(cfg_arch).any? do |f| - f.dynamic_location?(cfg_arch) + def format_changes_with_xlen?(design) + dynamic_length?(design) || + implemented_fields(design).any? do |f| + f.dynamic_location?(design) end end - # @param cfg_arch [ConfiguredArchitecture] A configuration + # @param design [Design] The design # @return [Array] List of functions reachable from this CSR's sw_read or a field's sw_write function - def reachable_functions(cfg_arch) + def reachable_functions(design) return @reachable_functions unless @reachable_functions.nil? fns = [] if has_custom_sw_read? - ast = pruned_sw_read_ast(cfg_arch) - symtab = cfg_arch.symtab.deep_clone + ast = pruned_sw_read_ast(design) + symtab = design.symtab.deep_clone symtab.push(ast) fns.concat(ast.reachable_functions(symtab)) end - if cfg_arch.multi_xlen? - implemented_fields_for(cfg_arch, 32).each do |field| - fns.concat(field.reachable_functions(cfg_arch, 32)) + if design.multi_xlen? + implemented_fields_for(design, 32).each do |field| + fns.concat(field.reachable_functions(design, 32)) end - implemented_fields_for(cfg_arch, 64).each do |field| - fns.concat(field.reachable_functions(cfg_arch, 64)) + implemented_fields_for(design, 64).each do |field| + fns.concat(field.reachable_functions(design, 64)) end else - implemented_fields_for(cfg_arch, cfg_arch.mxlen).each do |field| - fns.concat(field.reachable_functions(cfg_arch, cfg_arch.mxlen)) + implemented_fields_for(design, design.mxlen).each do |field| + fns.concat(field.reachable_functions(design, design.mxlen)) end end @reachable_functions = fns.uniq end - # @param cfg_arch [ConfiguredArchitecture] Architecture definition + # @param design [Design] # @return [Array] List of functions reachable from this CSR's sw_read or a field's sw_wirte function, irrespective of context - def reachable_functions_unevaluated(cfg_arch) + def reachable_functions_unevaluated(design) return @reachable_functions_unevaluated unless @reachable_functions_unevaluated.nil? fns = [] if has_custom_sw_read? - ast = sw_read_ast(cfg_arch) - fns.concat(ast.reachable_functions_unevaluated(cfg_arch)) + ast = sw_read_ast(design) + fns.concat(ast.reachable_functions_unevaluated(design)) end fields.each do |field| - fns.concat(field.reachable_functions_unevaluated(cfg_arch)) + fns.concat(field.reachable_functions_unevaluated(design)) end @reachable_functions_unevaluated = fns.uniq end - # @param cfg_arch [ConfiguredArchitecture] A configuration + # @param design [Design] The design # @return [Boolean] Whether or not the length of the CSR depends on a runtime value # (e.g., mstatus.SXL) - def dynamic_length?(cfg_arch) + def dynamic_length?(design) return false if @data["length"].is_a?(Integer) # when a CSR is only defined in one base, its length can't change @@ -117,21 +117,21 @@ def dynamic_length?(cfg_arch) when "MXLEN" # 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 - cfg_arch.mxlen.nil? + design.mxlen.nil? when "SXLEN" # dynamic if either we don't know SXLEN or SXLEN is explicitly mutable - [nil, 3264].include?(cfg_arch.param_values["SXLEN"]) + [nil, 3264].include?(design.param_values["SXLEN"]) when "VSXLEN" # dynamic if either we don't know VSXLEN or VSXLEN is explicitly mutable - [nil, 3264].include?(cfg_arch.param_values["VSXLEN"]) + [nil, 3264].include?(design.param_values["VSXLEN"]) else raise "Unexpected length" end end - # @param cfg_arch [ConfiguredArchitecture] Architecture definition + # @param design [Design] # @return [Integer] Smallest length of the CSR in any mode - def min_length(cfg_arch) + def min_length(design) case @data["length"] when "MXLEN", "SXLEN", "VSXLEN" 32 @@ -142,14 +142,14 @@ def min_length(cfg_arch) end end - # @param cfg_arch [ConfiguredArchitecture] A configuration (can be nil if the length is not dependent on a config parameter) + # @param design [Design] The design (can be nil if the length 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 cfg_arch (e.g., because SXLEN is unknown and +effective_xlen+ was not provided) - def length(cfg_arch, effective_xlen = nil) + # @return [nil] if the length cannot be determined from the design (e.g., because SXLEN is unknown and +effective_xlen+ was not provided) + def length(design, effective_xlen = nil) case @data["length"] when "MXLEN" - return cfg_arch.mxlen unless cfg_arch.mxlen.nil? + return design.mxlen unless design.mxlen.nil? if !@data["base"].nil? @data["base"] @@ -158,11 +158,11 @@ def length(cfg_arch, effective_xlen = nil) effective_xlen end when "SXLEN" - if cfg_arch.param_values.key?("SXLEN") - if cfg_arch.param_values["SXLEN"] == 3264 + if design.param_values.key?("SXLEN") + if design.param_values["SXLEN"] == 3264 effective_xlen else - cfg_arch.param_values["SXLEN"] + design.param_values["SXLEN"] end elsif !@data["base"].nil? # if this CSR is only available in one base, then we know its length @@ -172,11 +172,11 @@ def length(cfg_arch, effective_xlen = nil) effective_xlen end when "VSXLEN" - if cfg_arch.param_values.key?("VSXLEN") - if cfg_arch.param_values["VSXLEN"] == 3264 + if design.param_values.key?("VSXLEN") + if design.param_values["VSXLEN"] == 3264 effective_xlen else - cfg_arch.param_values["VSXLEN"] + design.param_values["VSXLEN"] end elsif !@data["base"].nil? # if this CSR is only available in one base, then we know its length @@ -193,28 +193,28 @@ def length(cfg_arch, effective_xlen = nil) end # @return [Integer] The largest length of this CSR in any valid mode/xlen for the config - def max_length(cfg_arch) + def max_length(design) return @data["base"] unless @data["base"].nil? case @data["length"] when "MXLEN" - cfg_arch.mxlen || 64 + design.mxlen || 64 when "SXLEN" - if cfg_arch.param_values.key?("SXLEN") - if cfg_arch.param_values["SXLEN"] == 3264 + if design.param_values.key?("SXLEN") + if design.param_values["SXLEN"] == 3264 64 else - cfg_arch.param_values["SXLEN"] + design.param_values["SXLEN"] end else 64 end when "VSXLEN" - if cfg_arch.param_values.key?("VSXLEN") - if cfg_arch.param_values["VSXLEN"] == 3264 + if design.param_values.key?("VSXLEN") + if design.param_values["VSXLEN"] == 3264 64 else - cfg_arch.param_values["VSXLEN"] + design.param_values["VSXLEN"] end else 64 @@ -254,10 +254,10 @@ def length_cond64 end end - # @param cfg_arch [ConfiguredArchitecture] A configuration + # @param design [Design] The design # @return [String] Pretty-printed length string - def length_pretty(cfg_arch, effective_xlen=nil) - if dynamic_length?(cfg_arch) + def length_pretty(design, effective_xlen=nil) + if dynamic_length?(design) cond = case @data["length"] when "MXLEN" @@ -272,14 +272,14 @@ def length_pretty(cfg_arch, effective_xlen=nil) if effective_xlen.nil? <<~LENGTH - #{length(cfg_arch, 32)} when #{cond.sub('%%', '0')} - #{length(cfg_arch, 64)} when #{cond.sub('%%', '1')} + #{length(design, 32)} when #{cond.sub('%%', '0')} + #{length(design, 64)} when #{cond.sub('%%', '1')} LENGTH else - "#{length(cfg_arch, effective_xlen)}-bit" + "#{length(design, effective_xlen)}-bit" end else - "#{length(cfg_arch)}-bit" + "#{length(design)}-bit" end end @@ -306,39 +306,39 @@ def description_html Asciidoctor.convert description end - # @param cfg_arch [ConfiguredArchitecture] A configuration + # @param design [Design] The design # @return [Array] All implemented fields for this CSR at the given effective XLEN, sorted by location (smallest location first) # Excluded any fields that are defined by unimplemented extensions or a base that is not effective_xlen - def implemented_fields_for(cfg_arch, effective_xlen) + def implemented_fields_for(design, effective_xlen) @implemented_fields_for ||= {} - key = [cfg_arch.name, effective_xlen].hash + key = [design.name, effective_xlen].hash return @implemented_fields_for[key] if @implemented_fields_for.key?(key) @implemented_fields_for[key] = - implemented_fields(cfg_arch).select do |f| + implemented_fields(design).select do |f| !f.key?("base") || f.base == effective_xlen end end - # @param cfg_arch [ConfiguredArchitecture] A configuration + # @param design [Design] The design # @return [Array] All implemented fields for this CSR # Excluded any fields that are defined by unimplemented extensions - def implemented_fields(cfg_arch) + def implemented_fields(design) return @implemented_fields unless @implemented_fields.nil? implemented_bases = - if cfg_arch.param_values["SXLEN"] == 3264 || - cfg_arch.param_values["UXLEN"] == 3264 || - cfg_arch.param_values["VSXLEN"] == 3264 || - cfg_arch.param_values["VUXLEN"] == 3264 + if design.param_values["SXLEN"] == 3264 || + design.param_values["UXLEN"] == 3264 || + design.param_values["VSXLEN"] == 3264 || + design.param_values["VUXLEN"] == 3264 [32, 64] else - [cfg_arch.param_values["XLEN"]] + [design.param_values["XLEN"]] end @implemented_fields = fields.select do |f| - f.exists_in_cfg?(cfg_arch) + f.exists_in_design?(design) end end @@ -377,15 +377,15 @@ def field(field_name) field_hash[field_name.to_s] end - # @param cfg_arch [ConfiguredArchitecture] A configuration + # @param design [Design] The design # @param effective_xlen [Integer] The effective XLEN to apply, needed when field locations change with XLEN in some mode # @return [Idl::BitfieldType] A bitfield type that can represent all fields of the CSR - def bitfield_type(cfg_arch, effective_xlen = nil) + def bitfield_type(design, effective_xlen = nil) Idl::BitfieldType.new( "Csr#{name.capitalize}Bitfield", - length(cfg_arch, effective_xlen), + length(design, effective_xlen), fields_for(effective_xlen).map(&:name), - fields_for(effective_xlen).map { |f| f.location(cfg_arch, effective_xlen) } + fields_for(effective_xlen).map { |f| f.location(design, effective_xlen) } ) end @@ -414,7 +414,7 @@ def type_checked_sw_read_ast(symtab) ) ast = sw_read_ast(symtab) - symtab.cfg_arch.idl_compiler.type_check( + symtab.design.idl_compiler.type_check( ast, symtab, "CSR[#{name}].sw_read()" @@ -425,7 +425,7 @@ def type_checked_sw_read_ast(symtab) end # @return [FunctionBodyAst] The abstract syntax tree of the sw_read() function - # @param cfg_arch [ConfiguredArchitecture] A configuration + # @param design [Design] The design def sw_read_ast(symtab) raise ArgumentError, "Argument should be a symtab" unless symtab.is_a?(Idl::SymbolTable) @@ -433,7 +433,7 @@ def sw_read_ast(symtab) return nil if @data["sw_read()"].nil? # now, parse the function - @sw_read_ast = symtab.cfg_arch.idl_compiler.compile_func_body( + @sw_read_ast = symtab.design.idl_compiler.compile_func_body( @data["sw_read()"], return_type: Idl::Type.new(:bits, width: 128), # big int to hold special return values name: "CSR[#{name}].sw_read()", @@ -450,14 +450,14 @@ def sw_read_ast(symtab) @sw_read_ast end - def pruned_sw_read_ast(cfg_arch) + def pruned_sw_read_ast(design) @pruned_sw_read_asts ||= {} - ast = @pruned_sw_read_asts[cfg_arch.name] + ast = @pruned_sw_read_asts[design.name] return ast unless ast.nil? - ast = type_checked_sw_read_ast(cfg_arch.symtab) + ast = type_checked_sw_read_ast(design.symtab) - symtab = cfg_arch.symtab.global_clone + symtab = design.symtab.global_clone symtab.push(ast) # all CSR instructions are 32-bit symtab.add( @@ -470,9 +470,9 @@ def pruned_sw_read_ast(cfg_arch) ) ast = ast.prune(symtab) - ast.freeze_tree(cfg_arch.symtab) + ast.freeze_tree(design.symtab) - cfg_arch.idl_compiler.type_check( + design.idl_compiler.type_check( ast, symtab, "CSR[#{name}].sw_read()" @@ -481,7 +481,7 @@ def pruned_sw_read_ast(cfg_arch) symtab.pop symtab.release - @pruned_sw_read_asts[cfg_arch.name] = ast + @pruned_sw_read_asts[design.name] = ast end # @example Result for an I-type instruction @@ -493,12 +493,12 @@ def pruned_sw_read_ast(cfg_arch) # {bits: 12, name: 'imm12', attr: [''], type: 6} # ]} # - # @param cfg_arch [ConfiguredArchitecture] A configuration + # @param design [Design] The design # @param effective_xlen [Integer,nil] Effective XLEN to use when CSR length is dynamic # @param exclude_unimplemented [Boolean] If true, do not create include unimplemented fields in the figure - # @param optional_type [Integer] Wavedrom type (Fill color) for fields that are optional (not mandatory) in a partially-specified cfg_arch + # @param optional_type [Integer] Wavedrom type (Fill color) for fields that are optional (not mandatory) in a partially-specified design # @return [Hash] A representation of the WaveDrom drawing for the CSR (should be turned into JSON for wavedrom) - def wavedrom_desc(cfg_arch, effective_xlen, exclude_unimplemented: false, optional_type: 2) + def wavedrom_desc(design, effective_xlen, exclude_unimplemented: false, optional_type: 2) desc = { "reg" => [] } @@ -506,60 +506,56 @@ def wavedrom_desc(cfg_arch, effective_xlen, exclude_unimplemented: false, option field_list = if exclude_unimplemented - implemented_fields_for(cfg_arch, effective_xlen) + implemented_fields_for(design, effective_xlen) else fields_for(effective_xlen) end - field_list.sort! { |a, b| a.location(cfg_arch, effective_xlen).min <=> b.location(cfg_arch, effective_xlen).min } + field_list.sort! { |a, b| a.location(design, effective_xlen).min <=> b.location(design, effective_xlen).min } field_list.each do |field| - if field.location(cfg_arch, effective_xlen).min != last_idx + 1 + if field.location(design, effective_xlen).min != last_idx + 1 # have some reserved space - n = field.location(cfg_arch, effective_xlen).min - last_idx - 1 - raise "negative reserved space? #{n} #{name} #{field.location(cfg_arch, effective_xlen).min} #{last_idx + 1}" if n <= 0 + n = field.location(design, effective_xlen).min - last_idx - 1 + raise "negative reserved space? #{n} #{name} #{field.location(design, effective_xlen).min} #{last_idx + 1}" if n <= 0 desc["reg"] << { "bits" => n, type: 1 } end - if cfg_arch.partially_configured? && field.optional_in_cfg?(cfg_arch) - desc["reg"] << { "bits" => field.location(cfg_arch, effective_xlen).size, "name" => field.name, type: optional_type } + if design.partially_configured? && field.optional_in_design?(design) + desc["reg"] << { "bits" => field.location(design, effective_xlen).size, "name" => field.name, type: optional_type } else - desc["reg"] << { "bits" => field.location(cfg_arch, effective_xlen).size, "name" => field.name, type: 3 } + desc["reg"] << { "bits" => field.location(design, effective_xlen).size, "name" => field.name, type: 3 } end - last_idx = field.location(cfg_arch, effective_xlen).max + last_idx = field.location(design, effective_xlen).max end - if !field_list.empty? && (field_list.last.location(cfg_arch, effective_xlen).max != (length(cfg_arch, effective_xlen) - 1)) + if !field_list.empty? && (field_list.last.location(design, effective_xlen).max != (length(design, effective_xlen) - 1)) # reserved space at the end - desc["reg"] << { "bits" => (length(cfg_arch, effective_xlen) - 1 - last_idx), type: 1 } + desc["reg"] << { "bits" => (length(design, effective_xlen) - 1 - last_idx), type: 1 } # desc['reg'] << { 'bits' => 1, type: 1 } end - desc["config"] = { "bits" => length(cfg_arch, effective_xlen) } - desc["config"]["lanes"] = length(cfg_arch, effective_xlen) / 16 + desc["config"] = { "bits" => length(design, effective_xlen) } + desc["config"]["lanes"] = length(design, effective_xlen) / 16 desc end - # @param cfg_arch [ConfiguredArchitecture] Architecture def + # @param design [Design] # @return [Boolean] whether or not the CSR is possibly implemented given the supplies config options - def exists_in_cfg?(cfg_arch) - if cfg_arch.fully_configured? - (@data["base"].nil? || (cfg_arch.possible_xlens.include? @data["base"])) && - cfg_arch.transitive_implemented_extensions.any? { |e| defined_by?(e) } + def exists_in_design?(design) + if design.fully_configured? + (@data["base"].nil? || (design.possible_xlens.include? @data["base"])) && + design.transitive_implemented_ext_vers.any? { |ext_ver| defined_by?(ext_ver) } else - (@data["base"].nil? || (cfg_arch.possible_xlens.include? @data["base"])) && - cfg_arch.prohibited_extensions.none? { |ext_req| ext_req.satisfying_versions.any? { |e| defined_by?(e) } } + (@data["base"].nil? || (design.possible_xlens.include? @data["base"])) && + design.prohibited_ext_reqs.none? { |ext_req| ext_req.satisfying_versions.any? { |ext_ver| defined_by?(ext_ver) } } end end - # @param cfg_arch [ConfiguredArchitecture] Architecture def + # @param design [Design] # @return [Boolean] whether or not the CSR is optional in the config - def optional_in_cfg?(cfg_arch) - raise "optional_in_cfg? should only be used by a partially-specified arch def" unless cfg_arch.partially_configured? + def optional_in_design?(design) + raise "optional_in_design? should only be used by a partially-specified arch def" unless design.partially_configured? - exists_in_cfg?(cfg_arch) && - cfg_arch.mandatory_extensions.all? do |ext_req| - ext_req.satisfying_versions.none? do |ext_ver| - defined_by?(ext_ver) - end - end + exists_in_design?(design) && + design.mandatory_ext_reqs.all? { |ext_req| ext_req.satisfying_versions.none? { |ext_ver| defined_by?(ext_ver) } } end end diff --git a/lib/arch_obj_models/csr_field.rb b/lib/arch_obj_models/csr_field.rb index df4b53261..b9168008e 100644 --- a/lib/arch_obj_models/csr_field.rb +++ b/lib/arch_obj_models/csr_field.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require_relative "obj" +require_relative "database_obj" require_relative "../idl/passes/gen_option_adoc" @@ -17,49 +17,43 @@ class CsrField < DatabaseObject # @return [Integer] The base XLEN required for this CsrField to exist. One of [32, 64] # @return [nil] if the CsrField exists in any XLEN - def base - @data["base"] - end + def base = @data["base"] # @param parent_csr [Csr] The Csr that defined this field # @param field_data [Hash] Field data from the arch spec def initialize(parent_csr, field_name, field_data) - super(field_data, parent_csr.data_path, arch: parent_csr.arch) + super(field_data, parent_csr.data_path, parent_csr.arch) @name = field_name @parent = parent_csr + @type_cache = {} end - # @param possible_xlens [Array] List of xlens that be used in any implemented mode - # @param extensions [Array] List of extensions implemented - # @return [Boolean] whether or not the instruction is implemented given the supplies config options - def exists_in_cfg?(cfg_arch) - if cfg_arch.fully_configured? - parent.exists_in_cfg?(cfg_arch) && - (@data["base"].nil? || cfg_arch.possible_xlens.include?(@data["base"])) && - (@data["definedBy"].nil? || cfg_arch.transitive_implemented_extensions.any? { |ext_ver| defined_by?(ext_ver) }) + # @param design [Design] The design + # @return [Boolean] whether or not the instruction is implemented given the supplied design + def exists_in_design?(design) + if design.fully_configured? + parent.exists_in_design?(design) && + (@data["base"].nil? || design.possible_xlens.include?(@data["base"])) && + (@data["definedBy"].nil? || design.transitive_implemented_ext_vers.any? { |ext_ver| defined_by?(ext_ver) }) else - raise "unexpected type" unless cfg_arch.partially_configured? + raise "unexpected type" unless design.partially_configured? - parent.exists_in_cfg?(cfg_arch) && - (@data["base"].nil? || cfg_arch.possible_xlens.include?(@data["base"])) && - (@data["definedBy"].nil? || cfg_arch.prohibited_extensions.none? { |ext_req| ext_req.satisfying_versions.any? { |ext_ver| defined_by?(ext_ver) } }) + parent.exists_in_design?(design) && + (@data["base"].nil? || design.possible_xlens.include?(@data["base"])) && + (@data["definedBy"].nil? || design.prohibited_ext_reqs.none? { |ext_req| ext_req.satisfying_versions.any? { |ext_ver| defined_by?(ext_ver) } }) end end - # @return [Boolean] For a partially configured cfg_arch, whether or not the field is optional (not mandatory or prohibited) - def optional_in_cfg?(cfg_arch) - raise "optional_in_cfg? should only be called on a partially configured cfg_arch" unless cfg_arch.partially_configured? + # @return [Boolean] For a partially configured design, whether or not the field is optional (not mandatory or prohibited) + def optional_in_design?(design) + raise "optional_in_design? should only be called on a partially configured design" unless design.partially_configured? - exists_in_cfg?(cfg_arch) && + exists_in_design?(design) && ( if data["definedBy"].nil? - parent.optional_in_cfg?(cfg_arch) + parent.optional_in_design?(design) else - cfg_arch.mandatory_extensions.all? do |ext_req| - ext_req.satisfying_versions.none? do |ext_ver| - defined_by?(ext_ver) - end - end + design.mandatory_ext_reqs.all? { |ext_req| ext_req.satisfying_versions.none? { |ext_ver| defined_by?(ext_ver) } } end ) end @@ -71,7 +65,7 @@ def type_ast(symtab) return @type_ast unless @type_ast.nil? return nil if @data["type()"].nil? - @type_ast = symtab.cfg_arch.idl_compiler.compile_func_body( + @type_ast = symtab.design.idl_compiler.compile_func_body( @data["type()"], name: "CSR[#{csr.name}].#{name}.type()", input_file: csr.__source, @@ -105,7 +99,7 @@ def type_checked_type_ast(symtab) ) ast = type_ast(symtab) - symtab.cfg_arch.idl_compiler.type_check( + symtab.design.idl_compiler.type_check( ast, symtab, "CSR[#{name}].type()" @@ -137,7 +131,7 @@ def pruned_type_ast(symtab) ast.freeze_tree(symtab) - symtab.cfg_arch.idl_compiler.type_check( + symtab.design.idl_compiler.type_check( ast, symtab, "CSR[#{name}].type()" @@ -147,7 +141,7 @@ def pruned_type_ast(symtab) @pruned_type_asts[symtab_hash] = ast end - # returns the definitive type for a configuration + # Returns the definitive type for the design part of the symbol table. # # @param symtab [SymbolTable] Symbol table # @return [String] @@ -161,11 +155,7 @@ def pruned_type_ast(symtab) def type(symtab) raise ArgumentError, "Argument 1 should be a symtab" unless symtab.is_a?(Idl::SymbolTable) - unless @type_cache.nil? - raise "Different cfg_arch for type #{@type_cache.keys}, #{symtab.cfg_arch}" unless @type_cache.key?(symtab.cfg_arch) - - return @type_cache[symtab.cfg_arch] - end + return @type_cache[symtab.design] if @type_cache.key?(symtab.design) type = if @data.key?("type") @@ -210,8 +200,7 @@ def type(symtab) # end end - @type_cache ||= {} - @type_cache[symtab.cfg_arch] = type + @type_cache[symtab.design] = type end # @return [String] A pretty-printed type string @@ -242,7 +231,7 @@ def alias range_start = Regexp.last_match(4) range_end = Regexp.last_match(5) - csr_field = cfg_arch.csr(csr_name).field(csr_field) + csr_field = design.csr(csr_name).field(csr_field) range = if range.nil? field.location @@ -256,32 +245,32 @@ def alias @alias end - # @return [Array] List of functions called through this field - # @param cfg_arch [ConfiguredArchitecture] a configuration + # @param design [Design] The design # @Param effective_xlen [Integer] 32 or 64; needed because fields can change in different XLENs - def reachable_functions(cfg_arch, effective_xlen) + # @return [Array] List of functions called thorough this field + def reachable_functions(design, effective_xlen) return @reachable_functions unless @reachable_functions.nil? symtab = - if (cfg_arch.configured?) - cfg_arch.symtab + if (design.configured?) + design.symtab else - raise ArgumentError, "Must supply effective_xlen for generic ConfiguredArchitecture" if effective_xlen.nil? + raise ArgumentError, "Must supply effective_xlen for generic Design" if effective_xlen.nil? if effective_xlen == 32 - cfg_arch.symtab_32 + design.symtab_32 else - cfg_arch.symtab_64 + design.symtab_64 end end fns = [] if has_custom_sw_write? - ast = pruned_sw_write_ast(cfg_arch, effective_xlen) + ast = pruned_sw_write_ast(design, effective_xlen) unless ast.nil? sw_write_symtab = symtab.deep_clone sw_write_symtab.push(ast) - sw_write_symtab.add("csr_value", Idl::Var.new("csr_value", csr.bitfield_type(symtab.cfg_arch, effective_xlen))) + sw_write_symtab.add("csr_value", Idl::Var.new("csr_value", csr.bitfield_type(symtab.design, effective_xlen))) fns.concat ast.reachable_functions(sw_write_symtab) end end @@ -334,18 +323,18 @@ def reachable_functions_unevaluated(symtab) # @return [Csr] Parent CSR for this field alias csr parent - # @param cfg_arch [ConfiguredArchitecture] A configuration + # @param design [Design] The design # @return [Boolean] Whether or not the location of the field changes dynamically # (e.g., based on mstatus.SXL) in the configuration - def dynamic_location?(cfg_arch) + def dynamic_location?(design) # 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| cfg_arch.multi_xlen_in_mode?(mode) } + csr.modes_with_access.any? { |mode| design.multi_xlen_in_mode?(mode) } end - # @param cfg_arch [IdL::Compiler] A compiler + # @param design [IdL::Compiler] A compiler # @return [Idl::FunctionBodyAst] Abstract syntax tree of the reset_value function # @return [nil] If the reset_value is not a function def reset_value_ast(symtab) @@ -354,7 +343,7 @@ def reset_value_ast(symtab) return @reset_value_ast unless @reset_value_ast.nil? return nil unless @data.key?("reset_value()") - @reset_value_ast = symtab.cfg_arch.idl_compiler.compile_func_body( + @reset_value_ast = symtab.design.idl_compiler.compile_func_body( @data["reset_value()"], return_type: Idl::Type.new(:bits, width: 64), name: "CSR[#{parent.name}].#{name}.reset_value()", @@ -383,7 +372,7 @@ def type_checked_reset_value_ast(symtab) symtab = symtab.deep_clone symtab.push(ast) symtab.add("__expected_return_type", Idl::Type.new(:bits, width: 64)) - symtab.cfg_arch.idl_compiler.type_check( + symtab.design.idl_compiler.type_check( ast, symtab, "CSR[#{csr.name}].reset_value()" @@ -416,7 +405,7 @@ def pruned_reset_value_ast(symtab) symtab.push(ast) symtab.add("__expected_return_type", Idl::Type.new(:bits, width: 64)) - symtab.cfg_arch.idl_compiler.type_check( + symtab.design.idl_compiler.type_check( ast, symtab, "CSR[#{csr.name}].#{name}.reset_value()" @@ -425,20 +414,20 @@ def pruned_reset_value_ast(symtab) @type_checked_reset_value_asts[symtab_hash] = ast end - # @param cfg_arch [ConfiguredArchitecture] A config + # @param design [Design] The design # @return [Integer] The reset value of this field # @return [String] The string 'UNDEFINED_LEGAL' if, for this config, there is no defined reset value - def reset_value(cfg_arch) - cached_value = @reset_value_cache.nil? ? nil : @reset_value_cache[cfg_arch] + def reset_value(design) + cached_value = @reset_value_cache.nil? ? nil : @reset_value_cache[design] return cached_value if cached_value @reset_value_cache ||= {} - @reset_value_cache[cfg_arch] = + @reset_value_cache[design] = if @data.key?("reset_value") @data["reset_value"] else - symtab = cfg_arch.symtab + symtab = design.symtab ast = pruned_reset_value_ast(symtab.deep_clone) val = ast.return_value(symtab.deep_clone.push(ast)) val = "UNDEFINED_LEGAL" if val == 0x1_0000_0000_0000_0000 @@ -446,22 +435,22 @@ def reset_value(cfg_arch) end end - def dynamic_reset_value?(cfg_arch) + def dynamic_reset_value?(design) return false unless @data["reset_value"].nil? value_result = Idl::AstNode.value_try do - reset_value(cfg_arch) + reset_value(design) false end || true end - def reset_value_pretty(cfg_arch) + def reset_value_pretty(design) str = nil value_result = Idl::AstNode.value_try do - str = reset_value(cfg_arch) + str = reset_value(design) end Idl::AstNode.value_else(value_result) do - ast = reset_value_ast(cfg_arch.symtab) + ast = reset_value_ast(design.symtab) str = ast.gen_option_adoc end str @@ -496,11 +485,11 @@ def type_checked_sw_write_ast(symtab, effective_xlen) ) symtab.add( "csr_value", - Idl::Var.new("csr_value", csr.bitfield_type(symtab.cfg_arch, effective_xlen)) + Idl::Var.new("csr_value", csr.bitfield_type(symtab.design, effective_xlen)) ) ast = sw_write_ast(symtab) - symtab.cfg_arch.idl_compiler.type_check( + symtab.design.idl_compiler.type_check( ast, symtab, "CSR[#{csr.name}].#{name}.sw_write()" @@ -512,7 +501,7 @@ def type_checked_sw_write_ast(symtab, effective_xlen) # @return [Idl::FunctionBodyAst] The abstract syntax tree of the sw_write() function # @return [nil] If there is no sw_write() function - # @param cfg_arch [ConfiguredArchitecture] An architecture definition + # @param symtab [Idl::SymbolTable] The symbol table def sw_write_ast(symtab) raise ArgumentError, "Argument should be a symtab" unless symtab.is_a?(Idl::SymbolTable) @@ -520,7 +509,7 @@ def sw_write_ast(symtab) return nil if @data["sw_write(csr_value)"].nil? # now, parse the function - @sw_write_ast = symtab.cfg_arch.idl_compiler.compile_func_body( + @sw_write_ast = symtab.design.idl_compiler.compile_func_body( @data["sw_write(csr_value)"], return_type: Idl::Type.new(:bits, width: 128), # big int to hold special return values name: "CSR[#{csr.name}].#{name}.sw_write(csr_value)", @@ -538,17 +527,17 @@ def sw_write_ast(symtab) # @return [Idl::FunctionBodyAst] The abstract syntax tree of the sw_write() function, type checked and pruned # @return [nil] if there is no sw_write() function # @param effective_xlen [Integer] effective xlen, needed because fields can change in different bases - # @param cfg_arch [ConfiguredArchitecture] A configuration - def pruned_sw_write_ast(cfg_arch, effective_xlen) + # @param design [Design] The design + def pruned_sw_write_ast(design, effective_xlen) @pruned_sw_write_asts ||= {} - ast = @pruned_sw_write_asts[cfg_arch.name] + ast = @pruned_sw_write_asts[design.name] return ast unless ast.nil? return nil unless @data.key?("sw_write(csr_value)") - raise ArgumentError, "cfg_arch must be configured to prune" if cfg_arch.unconfigured? + raise ArgumentError, "design must be configured to prune" if design.unconfigured? - symtab = cfg_arch.symtab.global_clone + symtab = design.symtab.global_clone symtab.push(ast) # all CSR instructions are 32-bit symtab.add( @@ -561,17 +550,17 @@ def pruned_sw_write_ast(cfg_arch, effective_xlen) ) symtab.add( "csr_value", - Idl::Var.new("csr_value", csr.bitfield_type(cfg_arch, effective_xlen)) + Idl::Var.new("csr_value", csr.bitfield_type(design, effective_xlen)) ) - ast = type_checked_sw_write_ast(cfg_arch.symtab, effective_xlen) + ast = type_checked_sw_write_ast(design.symtab, effective_xlen) ast = ast.prune(symtab) raise "Symbol table didn't come back at global + 1" unless symtab.levels == 2 - ast.freeze_tree(cfg_arch.symtab) + ast.freeze_tree(design.symtab) - cfg_arch.idl_compiler.type_check( + design.idl_compiler.type_check( ast, symtab, "CSR[#{name}].sw_write(csr_value)" @@ -580,13 +569,13 @@ def pruned_sw_write_ast(cfg_arch, effective_xlen) symtab.pop symtab.release - @pruned_sw_write_asts[cfg_arch.name] = ast + @pruned_sw_write_asts[design.name] = ast end - # @param cfg_arch [ConfiguredArchitecture] A config. May be nil if the location is not configturation-dependent + # @param design [Design] The design. May be nil if the location is not design-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(cfg_arch, effective_xlen = nil) + def location(design, effective_xlen = nil) key = if @data.key?("location") "location" @@ -599,14 +588,14 @@ def location(cfg_arch, effective_xlen = nil) raise "Missing location for #{csr.name}.#{name} (#{key})?" unless @data.key?(key) if @data[key].is_a?(Integer) - csr_length = csr.length(cfg_arch, effective_xlen || @data["base"]) + csr_length = csr.length(design, 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(cfg_arch) - raise "Location (#{key} = #{@data[key]}) is past the max csr length (#{csr.max_length(cfg_arch)}) in #{csr.name}.#{name}" + if @data[key] > csr.max_length(design) + raise "Location (#{key} = #{@data[key]}) is past the max csr length (#{csr.max_length(design)}) in #{csr.name}.#{name}" end elsif @data[key] > csr_length - raise "Location (#{key} = #{@data[key]}) is past the csr length (#{csr.length(cfg_arch, effective_xlen)}) in #{csr.name}.#{name}" + raise "Location (#{key} = #{@data[key]}) is past the csr length (#{csr.length(design, effective_xlen)}) in #{csr.name}.#{name}" end @data[key]..@data[key] @@ -614,11 +603,11 @@ def location(cfg_arch, effective_xlen = nil) e, s = @data[key].split("-").map(&:to_i) raise "Invalid location" if s > e - csr_length = csr.length(cfg_arch, effective_xlen || @data["base"]) + csr_length = csr.length(design, 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(cfg_arch) - raise "Location (#{key} = #{@data[key]}) is past the max csr length (#{csr.max_length(cfg_arch)}) in #{csr.name}.#{name}" + if e > csr.max_length(design) + raise "Location (#{key} = #{@data[key]}) is past the max csr length (#{csr.max_length(design)}) 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}" @@ -641,11 +630,11 @@ def defined_in_base64? = @data["base"].nil? || @data["base"] == 64 # @return [Boolean] Whether or not this field exists for any XLEN def defined_in_all_bases? = @data["base"].nil? - # @param cfg_arch [ConfiguredArchitecture] A config. May be nil if the width of the field is not configuration-dependent + # @param design [Design] The design. May be nil if the width of the field is not design-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 [Integer] Number of bits in the field - def width(cfg_arch, effective_xlen) - location(cfg_arch, effective_xlen).size + def width(design, effective_xlen) + location(design, effective_xlen).size end def location_cond32 @@ -675,14 +664,14 @@ def location_cond64 end # @return [String] Pretty-printed location string - def location_pretty(cfg_arch, effective_xlen = nil) + def location_pretty(design, effective_xlen = nil) derangeify = proc { |loc| next loc.min.to_s if loc.size == 1 "#{loc.max}:#{loc.min}" } - if dynamic_location?(cfg_arch) + if dynamic_location?(design) condition = case csr.priv_mode when "M" @@ -697,14 +686,14 @@ def location_pretty(cfg_arch, effective_xlen = nil) if effective_xlen.nil? <<~LOC - * #{derangeify.call(location(cfg_arch, 32))} when #{condition.sub('%%', '0')} - * #{derangeify.call(location(cfg_arch, 64))} when #{condition.sub('%%', '1')} + * #{derangeify.call(location(design, 32))} when #{condition.sub('%%', '0')} + * #{derangeify.call(location(design, 64))} when #{condition.sub('%%', '1')} LOC else - derangeify.call(location(cfg_arch, effective_xlen)) + derangeify.call(location(design, effective_xlen)) end else - derangeify.call(location(cfg_arch, cfg_arch.mxlen)) + derangeify.call(location(design, design.mxlen)) end end @@ -758,7 +747,7 @@ def location_pretty(cfg_arch, effective_xlen = nil) }.freeze # @return [String] Long description of the field type - def type_desc(cfg_arch) - TYPE_DESC_MAP[type(cfg_arch.symtab)] + def type_desc(design) + TYPE_DESC_MAP[type(design.symtab)] end end diff --git a/lib/arch_obj_models/obj.rb b/lib/arch_obj_models/database_obj.rb similarity index 63% rename from lib/arch_obj_models/obj.rb rename to lib/arch_obj_models/database_obj.rb index e1ec1a01a..31721cb36 100644 --- a/lib/arch_obj_models/obj.rb +++ b/lib/arch_obj_models/database_obj.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -# base for any object representation of the Architecture Definition +# Base class for any object representation of the Architecture. # does two things: # # 1. Makes the raw data for the object accessible via [] @@ -25,7 +25,87 @@ # is warranted, e.g., the CSR Field 'alias' returns a CsrFieldAlias object # instead of a simple string class DatabaseObject - # Exception raised when there is a problem with a schema file + attr_reader :data, :data_path, :name, :long_name, :description + + # @return [Architecture] Architectural standards + attr_reader :arch + + def kind = @data["kind"] + + # @param data [Hash] Hash with fields to be added + # @param data_path [Pathname] Path to the data file + # @param arch [Architecture] Architectural standards + def initialize(data, data_path, arch) + raise ArgumentError, "Bad data" unless data.is_a?(Hash) + raise ArgumentError, "Need Architecture class but it's a #{arch.class}" unless arch.is_a?(Architecture) + + @data = data + @data_path = data_path + @arch = arch + @name = data["name"] + @long_name = data["long_name"] + @description = data["description"] + end + + # @return [Array] + def cert_coverage_points + return @cert_coverage_points unless @cert_coverage_points.nil? + + @cert_coverage_points = [] + @data["cert_coverage_points"]&.each do |cert_data| + @cert_coverage_points << CertCoveragePoint.new(cert_data, self) + end + @cert_coverage_points + end + + # @return [Hash] Hash with ID as key of all coverage points defined by database object + def cert_coverage_point_hash + return @cert_coverage_point_hash unless @cert_coverage_point_hash.nil? + + @cert_coverage_point_hash = {} + cert_coverage_points.each do |cp| + @cert_coverage_point_hash[cp.id] = cp + end + @cert_coverage_point_hash + end + + # @param id [String] Unique ID for the coverage point + # @return [CertCoveragePoint] + # @return [nil] if there is no certification coverage pointed with ID of +id+ + def cert_coverage_point(id) + cert_coverage_point_hash[id] + end + + # @return [Array] + def cert_test_procedures + return @cert_test_procedures unless @cert_test_procedures.nil? + + @cert_test_procedures = [] + @data["cert_test_procedures"]&.each do |cert_data| + @cert_test_procedures << CertTestProcedure.new(cert_data, self) + end + @cert_test_procedures + end + + # @return [Hash] Hash of all coverage points defined by database object + def cert_test_procedure_hash + return @cert_test_procedure_hash unless @cert_test_procedure_hash.nil? + + @cert_test_procedure_hash = {} + cert_test_procedures.each do |tp| + @cert_test_procedure_hash[tp.id] = tp + end + @cert_test_procedure_hash + end + + # @param id [String] Unique ID for test procedure + # @return [CertTestProcedure] + # @return [nil] if there is no certification test procedure with ID +id+ + def cert_test_procedure(id) + cert_test_procedure_hash[id] + end + + # Exception raised when there is a problem with a schema file class SchemaError < ::StandardError # result from JsonSchemer.validate attr_reader :result @@ -90,20 +170,7 @@ def initialize(path, result) end end - attr_reader :data, :data_path, :name, :long_name, :description - - # @return [Architecture] If only a specification (no config) is known - # @return [ConfiguredArchitecture] If a specification and config is known - # @return [nil] If neither is known - attr_reader :arch # Use when Architecture class is sufficient - - # @return [ConfiguredArchitecture] If a specification and config is known - # @return [nil] Otherwise - attr_reader :cfg_arch # Use when extra stuff provided by ConfiguredArchitecture is required - - def kind = @data["kind"] - - @@schemas ||= {} + @@schemas ||= {} @@schema_ref_resolver ||= proc do |pattern| if pattern.to_s =~ /^http/ JSON.parse(Net::HTTP.get(pattern)) @@ -180,28 +247,10 @@ def __source # @return [String] An extension name # @return [Array(String, Number)] An extension name and versions # @return [Array<*>] A list of extension names or extension names and versions - def definedBy - @data["definedBy"] - end - - # @param data [Hash] Hash with fields to be added - # @param data_path [Pathname] Path to the data file - def initialize(data, data_path, arch: nil) - raise ArgumentError, "Bad data" unless data.is_a?(Hash) - - @data = data - @data_path = data_path - if arch.is_a?(ConfiguredArchitecture) - @cfg_arch = arch - end - @arch = arch - @name = data["name"] - @long_name = data["long_name"] - @description = data["description"] - end + def definedBy = @data["definedBy"] def inspect - self.class.name + self.class.name + "##{name}" end # make the underlying YAML description available with [] @@ -215,13 +264,23 @@ def keys = @data.keys # @return (see Hash#key?) def key?(k) = @data.key?(k) + # @param ext_ver [ExtensionVersion] Version of the extension + # @param design [Design] The backend design + # @return [Boolean] Whether or not the object is defined-by the given ExtensionVersion in the given Design. + def in_scope?(ext_ver, design) + raise ArgumentError, "Require an ExtensionVersion object but got a #{ext_ver.class} object" unless ext_ver.is_a?(ExtensionVersion) + raise ArgumentError, "Require an IDesign object but got a #{design.class} object" unless design.is_a?(IDesign) + + defined_by?(ext_ver) + end + # @overload defined_by?(ext_name, ext_version) # @param ext_name [#to_s] An extension name # @param ext_version [#to_s] A specific extension version - # @return [Boolean] Whether or not the instruction is defined by extension `ext`, version `version` + # @return [Boolean] Whether or not the object is defined by extension `ext`, version `version` # @overload defined_by?(ext_version) # @param ext_version [ExtensionVersion] An extension version - # @return [Boolean] Whether or not the instruction is defined by ext_version + # @return [Boolean] Whether or not the object is defined by ext_version def defined_by?(*args) ext_ver = if args.size == 1 @@ -237,7 +296,7 @@ def defined_by?(*args) raise ArgumentError, "Unsupported number of arguments (#{args.size})" end - defined_by_condition.satisfied_by? { |req| req.satisfied_by?(ext_ver) } + defined_by_condition.satisfied_by? { |ext_req| ext_req.satisfied_by?(ext_ver) } end # because of multiple ("allOf") conditions, we generally can't return a list of extension versions here.... @@ -376,7 +435,7 @@ def <=>(other) end end -# represents a JSON Schema compoisition, e.g.: +# represents a JSON Schema composition, e.g.: # # anyOf: # - oneOf: @@ -390,7 +449,7 @@ def initialize(composition_hash, arch) raise ArgumentError, "composition_hash is nil" if composition_hash.nil? unless is_a_condition?(composition_hash) - raise ArgumentError, "Expecting a JSON schema comdition (got #{composition_hash})" + raise ArgumentError, "Expecting a JSON schema condition (got #{composition_hash})" end @hsh = composition_hash @@ -447,23 +506,23 @@ def flat_op def flat_versions case @hsh when String - [ExtensionRequirement.new(@hsh, arch: @arch)] + [ExtensionRequirement.new(@hsh, @arch)] when Hash if @hsh.key?("name") if @hsh.key?("version").nil? - [ExtensionRequirement.new(@hsh["name"], arch: @arch)] + [ExtensionRequirement.new(@hsh["name"], @arch)] else - [ExtensionRequirement.new(@hsh["name"], @hsh["version"], arch: @arch)] + [ExtensionRequirement.new(@hsh["name"], @hsh["version"], @arch)] end else @hsh[@hsh.keys.first].map do |r| if r.is_a?(String) - ExtensionRequirement.new(r, arch: @arch) + ExtensionRequirement.new(r, @arch) else if r.key?("version").nil? - ExtensionRequirement.new(r["name"], arch: @arch) + ExtensionRequirement.new(r["name"], @arch) else - ExtensionRequirement.new(r["name"], r["version"], arch: @arch) + ExtensionRequirement.new(r["name"], r["version"], @arch) end end end @@ -494,6 +553,12 @@ def to_asciidoc(cond = @hsh, indent = 0) end end + # @overload is_a_condition?(hsh) + # @param hsh [String] Extension name (case sensitive) + # @return [Boolean] True + # @overload is_a_condition?(hsh) + # @param hsh [Hash] Extension name (case sensitive) + # @return [Boolean] True if hash is a JSON schema condition def is_a_condition?(hsh) case hsh when String @@ -529,13 +594,13 @@ def is_a_condition?(hsh) def first_requirement(req = @hsh) case req when String - ExtensionRequirement.new(req, arch: @arch) + ExtensionRequirement.new(req, @arch) when Hash if req.key?("name") if req["version"].nil? - ExtensionRequirement.new(req["name"], arch: @arch) + ExtensionRequirement.new(req["name"], @arch) else - ExtensionRequirement.new(req["name"], req["version"], arch: @arch) + ExtensionRequirement.new(req["name"], req["version"], @arch) end else first_requirement(req[req.keys[0]]) @@ -574,7 +639,7 @@ def minimize(hsh = @hsh) min_ary = hsh["oneOf"].map { |element| minimize(element) } key = "oneOf" end - min_ary = min_ary.uniq! + min_ary = min_ary.uniq if min_ary.size == 1 min_ary.first else @@ -591,14 +656,14 @@ def to_rb_helper(hsh) if hsh.key?("name") if hsh.key?("version") if hsh["version"].is_a?(String) - "(yield ExtensionRequirement.new('#{hsh["name"]}', '#{hsh["version"]}', arch: @arch))" + "(yield ExtensionRequirement.new('#{hsh["name"]}', '#{hsh["version"]}', @arch))" elsif hsh["version"].is_a?(Array) - "(yield ExtensionRequirement.new('#{hsh["name"]}', #{hsh["version"].map { |v| "'#{v}'" }.join(', ')}, arch: @arch))" + "(yield ExtensionRequirement.new('#{hsh["name"]}', #{hsh["version"].map { |v| "'#{v}'" }.join(', ')}, @arch))" else raise "unexpected" end else - "(yield ExtensionRequirement.new('#{hsh["name"]}', arch: @arch))" + "(yield ExtensionRequirement.new('#{hsh["name"]}', @arch))" end else key = hsh.keys[0] @@ -619,7 +684,7 @@ def to_rb_helper(hsh) end end else - "(yield ExtensionRequirement.new('#{hsh}', arch: @arch))" + "(yield ExtensionRequirement.new('#{hsh}', @arch))" end end @@ -627,7 +692,6 @@ def to_rb_helper(hsh) # return a string that can be eval'd to determine if the objects in +ary_name+ # meet the Condition # - # @param ary_name [String] Name of a ruby string in the eval binding # @return [Boolean] If the condition is met def to_rb to_rb_helper(@hsh) @@ -655,7 +719,10 @@ def satisfied_by?(&block) raise ArgumentError, "Expecting one argument to block" unless block.arity == 1 - eval to_rb + # Written to allow debug breakpoints on individual lines. + to_rb_expr = to_rb + ret = eval to_rb_expr + ret end def satisfying_ext_versions @@ -681,3 +748,160 @@ def flat? = false def to_h = {} def minimize = {} end + +class CertCoveragePoint + # @param data [Hash] Data from YAML file + # @param db_obj [DatabaseObject] + def initialize(data, db_obj) + raise ArgumentError, "Need Hash but was passed a #{data.class}" unless data.is_a?(Hash) + raise ArgumentError, "Need DatabaseObject but was passed a #{db_obj.class}" unless db_obj.is_a?(DatabaseObject) + + @data = data + @db_obj = db_obj + + raise ArgumentError, "Missing certification coverage point name for #{db_obj.name} of kind #{db_obj.kind}" if name.nil? + raise ArgumentError, "Missing certification coverage point description for #{db_obj.name} of kind #{db_obj.kind}" if description.nil? + raise ArgumentError, "Missing certification coverage point ID for #{db_obj.name} of kind #{db_obj.kind}" if id.nil? + end + + # @return [String] Name of the coverage point + def name = @data["name"] + + # @return [String] Description of coverage point (could be multiple lines) + def description = @data["description"] + + # @return [String] Unique ID of the coverage point + def id = @data["id"] + + # @return [Array] List of certification point documentation links + def doc_links + return @doc_links unless @doc_links.nil? + + @doc_links = [] + @data["doc_links"]&.each do |dst| + @doc_links << DocLink.new(dst, @db_obj) + end + + raise "Missing doc_links for certification coverage point ID '#{id}' of kind #{@db_obj.kind}" if @doc_links.empty? + + @doc_links + end +end + +# Creates links into RISC-V documentation with the following formats for the destination link: +# +# Documenation Format +# ============ =============================================================== +# ISA manuals manual:ext:: +# manual:inst:: +# manual:insts:[-]+: +# manual:inst_group:: +# manual:csr:: +# manual:csr_field::: +# manual:param::: +# where is the location within the ISA manual documentation +# UDB doc udb:doc:ext: +# udb:doc:ext_param:: +# udb:doc:inst: +# udb:doc:csr: +# udb:doc:csr_field:: +# udb:doc:func: (Documentation of common/built-in IDL functions) +# udb:doc:cov_pt:: +# where is: +# sep for UDB documentation that "separates" coverage points from test plans +# combo for UDB documentation that "combines" coverage points with test plans +# appendix for UDB documentation that has coverage points and test plans in appendices +# where is the ID of the coverage point +# IDL code idl:code:inst:: +# TODO for CSR and CSR Fields +class DocLink + # @param dst_link [String] The documentation link provided in the YAML + # @param db_obj [String] Database object + def initialize(dst_link, db_obj) + raise ArgumentError, "Need String but was passed a #{data.class}" unless dst_link.is_a?(String) + @dst_link = dst_link + + raise ArgumentError, "Missing documentation link for #{db_obj.name} of kind #{db_obj.kind}" if @dst_link.nil? + end + + # @return [String] Unique ID of the linked to coverage point + def dst_link = @dst_link + + # @return [String] Asciidoc to create desired link. + def to_adoc + "<<#{@dst_link},#{@dst_link}>>" + end +end + +class CertTestProcedure + # @param data [Hash] Data from YAML file + # @param db_obj [DatabaseObject] + def initialize(data, db_obj) + raise ArgumentError, "Need Hash but was passed a #{data.class}" unless data.is_a?(Hash) + raise ArgumentError, "Need DatabaseObject but was passed a #{db_obj.class}" unless db_obj.is_a?(DatabaseObject) + + @data = data + @db_obj = db_obj + + raise ArgumentError, "Missing certification test procedure name for #{db_obj.name} of kind #{db_obj.kind}" if name.nil? + raise ArgumentError, "Missing certification test procedure description for #{db_obj.name} of kind #{db_obj.kind}" if description.nil? + raise ArgumentError, "Missing certification test procedure ID for #{db_obj.name} of kind #{db_obj.kind}" if id.nil? + end + + # @return [String] Name of the test procedure + def name = @data["name"] + + # @return [String] Description of test procedure (could be multiple lines) + def description = @data["description"] + + # @return [String] Unique ID of the test procedure + def id = @data["id"] + + # @return [Array] + def cert_coverage_points + return @cert_coverage_points unless @cert_coverage_points.nil? + + @cert_coverage_points = [] + @data["coverage_points"]&.each do |id| + cp = @db_obj.cert_coverage_point(id) + raise ArgumentError, "Can't find certification test procedure with ID '#{id}' for '#{@db_obj.name}' of kind #{@db_obj.kind}" if cp.nil? + @cert_coverage_points << cp + end + @cert_coverage_points + end + + # @return [Array] List of certification test procedure steps + def cert_steps + return @cert_steps unless @cert_steps.nil? + + @cert_steps = [] + @data["steps"]&.each do |step_data| + @cert_steps << CertStep.new(step_data, @db_obj) + end + + raise "No steps for certification procedure ID '#{id}' of kind #{@db_obj.kind}" if @cert_steps.empty? + + @cert_steps + end +end + +class CertStep + # @param data [Hash] The step information from the YAML + def initialize(data, db_obj) + raise ArgumentError, "Need Hash but was passed a #{data.class}" unless data.is_a?(Hash) + @data = data + + raise ArgumentError, "Missing certification step name for #{db_obj.name} of kind #{db_obj.kind}" if name.nil? + raise ArgumentError, "Missing certification step description for #{db_obj.name} of kind #{db_obj.kind}" if description.nil? + end + + # @return [String] Name of the step + def name = @data["name"] + + # @return [String] Description of the step + def description = @data["description"] + + # @return [String] Optional note (can be nil) + # @return [nil] Optional + def note = @data["note"] +end diff --git a/lib/arch_obj_models/exception_code.rb b/lib/arch_obj_models/exception_code.rb index 2700ba362..082dad310 100644 --- a/lib/arch_obj_models/exception_code.rb +++ b/lib/arch_obj_models/exception_code.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -# a synchroncous exception code +# A synchroncous exception code class ExceptionCode # @return [String] Long-form display name (can include special characters) attr_reader :name @@ -23,5 +23,5 @@ def initialize(name, var, number, ext) end end -# all the same information as ExceptinCode, but for interrupts +# Define InterruptCode class (all the same members as ExceptionCode) InterruptCode = Class.new(ExceptionCode) diff --git a/lib/arch_obj_models/extension.rb b/lib/arch_obj_models/extension.rb index 120154da7..1e404a68f 100644 --- a/lib/arch_obj_models/extension.rb +++ b/lib/arch_obj_models/extension.rb @@ -1,132 +1,10 @@ # frozen_string_literal: true -require_relative "obj" +require_relative "database_obj" +require_relative "parameter" require_relative "schema" require_relative "../version" -# A parameter (AKA option, AKA implementation-defined value) supported by an extension -class ExtensionParameter - # @return [Architecture] The defining architecture - attr_reader :arch - - # @return [String] Parameter name - attr_reader :name - - # @return [String] Asciidoc description - attr_reader :desc - - # @return [Schema] JSON Schema for this param - attr_reader :schema - - # @return [String] Ruby code to perform validation above and beyond JSON schema - # @return [nil] If there is no extra validation - attr_reader :extra_validation - - # @return [Array] The extension(s) that define this parameter - # - # Some parameters are defined by multiple extensions (e.g., CACHE_BLOCK_SIZE by Zicbom and Zicboz). - # When defined in multiple places, the parameter *must* mean the exact same thing. - attr_reader :exts - - # @returns [Idl::Type] Type of the parameter - attr_reader :idl_type - - # Pretty convert extension schema to a string. - def schema_type - @schema.to_pretty_s - end - - # @param ext [Extension] - # @param name [String] - # @param data [Hash>" - else - name - end - end - - # sorts by name - def <=>(other) - raise ArgumentError, "ExtensionParameters are only comparable to other extension parameters" unless other.is_a?(ExtensionParameter) - - @name <=> other.name - end -end - -class ExtensionParameterWithValue - # @return [Object] The parameter value - attr_reader :value - - # @return [String] Parameter name - def name = @param.name - - # @return [String] Asciidoc description - def desc = @param.desc - - # @return [Hash] JSON Schema for the parameter value - def schema = @param.schema - - # @return [String] Ruby code to perform validation above and beyond JSON schema - # @return [nil] If there is no extra validatino - def extra_validation = @param.extra_validation - - # @return [Extension] The extension that defines this parameter - def exts = @param.exts - - def initialize(param, value) - @param = param - @value = value - end -end - # Extension definition class Extension < DatabaseObject # @return [String] Long name of the extension @@ -178,14 +56,14 @@ def min_ratified_version ratified_versions.min { |a, b| a.version_spec <=> b.version_spec } end - # @return [Array] List of parameters added by this extension + # @return [Array] List of parameters added by this extension def params return @params unless @params.nil? @params = [] if @data.key?("params") @data["params"].each do |param_name, param_data| - @params << ExtensionParameter.new(self, param_name, param_data) + @params << Parameter.new(self, param_name, param_data) end end @params @@ -195,9 +73,9 @@ def params # @return [Array] Array of extensions implied by any version of this extension meeting version_requirement def implies(version_requirement = nil) if version_requirement.nil? - return [] unless ExtensionRequirement.new(@new, arch: @arch).satisfied_by?(max_version.version) + return [] unless ExtensionRequirement.new(@new, @arch).satisfied_by?(max_version.version) else - return [] unless ExtensionRequirement.new(@new, version_requirement, arch: @arch).satisfied_by?(max_version.version) + return [] unless ExtensionRequirement.new(@new, version_requirement, @arch).satisfied_by?(max_version.version) end max_version.implications @@ -208,15 +86,15 @@ def conflicts return [] if @data["conflicts"].nil? if @data["conflicts"].is_a?(String) - [ExtensionRequirement.new(@data["conflicts"], arch: @arch)] + [ExtensionRequirement.new(@data["conflicts"], @arch)] elsif @data["conflicts"].is_a?(Hash) - [ExtensionRequirement.new(@data["conflicts"]["name"], @data["conflicts"]["version"], arch: @arch)] + [ExtensionRequirement.new(@data["conflicts"]["name"], @data["conflicts"]["version"], @arch)] elsif @data["conflicts"].is_a?(Array) @data["conflicts"].map do |conflict| if conflict.is_a?(String) - ExtensionRequirement.new(conflict, arch: @arch) + ExtensionRequirement.new(conflict, @arch) elsif conflict.is_a?(Array) - ExtensionRequirement.new(conflict["name"], conflict["version"], arch: @arch) + ExtensionRequirement.new(conflict["name"], conflict["version"], @arch) else raise "Invalid conflicts data: #{conflict.inspect}" end @@ -258,10 +136,8 @@ def reachable_functions(symtab) funcs += inst.reachable_functions(symtab, 64) if inst.defined_in_base?(64) end - # The one place in this file that needs a ConfiguredArchitecture object instead of just Architecture. - raise "In #{name}, need to provide ConfiguredArchitecture" if cfg_arch.nil? csrs.each do |csr| - funcs += csr.reachable_functions(cfg_arch) + funcs += csr.reachable_functions(design) end @reachable_functions[symtab] = funcs.uniq @@ -365,7 +241,7 @@ def contributors @contributors end - # @return [Array] The list of parameters for this extension version + # @return [Array] The list of parameters for this extension version def params @ext.params.select { |p| p.defined_in_extension_version?(self) } end @@ -449,7 +325,7 @@ def implications @implications end - # @return [Array] List of extension versions that are implied by with this ExtensionVersion + # @return [Array] List of extension versions that are implied by with this ExtensionVersion. # This list is transitive; if an implication I1 implies another extension I2, # both I1 and I2 are in the returned list def transitive_implications @@ -483,7 +359,7 @@ def transitive_implications # @param ext_version_requirements [String,Array] Extension version requirements # @return [Boolean] whether or not this ExtensionVersion is named `ext_name` and satisfies the version requirements def satisfies?(ext_name, *ext_version_requirements) - ExtensionRequirement.new(ext_name, ext_version_requirements).satisfied_by?(self) + ExtensionRequirement.new(ext_name, ext_version_requirements, arch).satisfied_by?(self) end # sorts extension by name, then by version @@ -499,7 +375,7 @@ def <=>(other) end end - # @return [Array] the list of CSRs implemented by this extension version (may be empty) + # @return [Array] List of CSRs implemented by this extension version (may be empty) def implemented_csrs return @implemented_csrs unless @implemented_csrs.nil? @@ -508,7 +384,7 @@ def implemented_csrs end end - # @return [Array] the list of insts implemented by this extension version (may be empty) + # @return [Array] List of insts implemented by this extension version (may be empty) def implemented_instructions return @implemented_instructions unless @implemented_instructions.nil? @@ -516,6 +392,34 @@ def implemented_instructions inst.defined_by?(self) end end + + # @param design [Design] The design + # @return [Array] List of CSRs in-scope for this design for this extension version (may be empty). + # Factors in effect of design's xlen in the appropriate mode for the CSR. + def in_scope_csrs(design) + raise ArgumentError, "Require an IDesign object but got a #{design.class} object" unless design.is_a?(IDesign) + + return @in_scope_csrs unless @in_scope_csrs.nil? + + @in_scope_csrs = @arch.csrs.select do |csr| + csr.defined_by?(self) && + (csr.base.nil? || (design.possible_xlens.include?(csr.base))) + end + end + + # @param design [Design] The design + # @return [Array] List of instructions in-scope for this design for this extension version (may be empty). + # Factors in effect of design's xlen in the appropriate mode for the instruction. + def in_scope_instructions(design) + raise ArgumentError, "Require an IDesign object but got a #{design.class} object" unless design.is_a?(IDesign) + + return @in_scope_instructions unless @in_scope_instructions.nil? + + @in_scope_instructions = @arch.instructions.select do |inst| + inst.defined_by?(self) && + (inst.base.nil? || (design.possible_xlens.include?(inst.base))) + end + end end # Is the extension mandatory, optional, various kinds of optional, etc. @@ -687,27 +591,23 @@ def to_s "#{name} " + requirement_specs_to_s end - # @return [Extension] The extension that this requirement is for - def extension - return @extension unless @extension.nil? - - raise "Cannot get extension; arch was not initialized" if @arch.nil? - - @extension = @arch.extension(@name) - end + # @return [Extension] The extension corresponding to this requirement + def extension = @ext # @param name [#to_s] Extension name # @param requirements [String] Single requirement # @param requirements [Array] List of requirements, all of which must hold - # @param arch [Architecture] - def initialize(name, *requirements, arch: nil, note: nil, req_id: nil, presence: nil) + # @param arch [Architecture] The architecture database + def initialize(name, *requirements, arch, note: nil, req_id: nil, presence: nil) raise ArgumentError, "For #{name}, arch not allowed to be nil" if arch.nil? raise ArgumentError, "For #{name}, Architecture is required" unless arch.is_a?(Architecture) @name = name.to_s.freeze @arch = arch @ext = @arch.extension(@name) - raise ArgumentError, "Could not find extension named '#{@name}'" if @ext.nil? + if @ext.nil? + raise ArgumentError, "Could not find extension named '#{@name}'" + end requirements = if requirements.empty? @@ -724,10 +624,9 @@ def initialize(name, *requirements, arch: nil, note: nil, req_id: nil, presence: # @return [Array] The list of extension versions that satisfy this extension requirement def satisfying_versions - ext = @arch.extension(@name) - return [] if ext.nil? + return @satisfying_versions unless @satisfying_versions.nil? - ext.versions.select { |v| satisfied_by?(v) } + @satisfying_versions = @ext.versions.select { |v| satisfied_by?(v) } end # @overload diff --git a/lib/arch_obj_models/instruction.rb b/lib/arch_obj_models/instruction.rb index 077a60f6c..72bed1eb6 100644 --- a/lib/arch_obj_models/instruction.rb +++ b/lib/arch_obj_models/instruction.rb @@ -2,7 +2,7 @@ require 'ruby-prof-flamegraph' -require_relative "obj" +require_relative "database_obj" # model of a specific instruction in a specific base (RV32/RV64) @@ -137,25 +137,25 @@ def fill_symtab(global_symtab, effective_xlen, ast) def pruned_operation_ast(global_symtab, effective_xlen) @pruned_asts ||= {} - cfg_arch = global_symtab.cfg_arch + design = global_symtab.design - pruned_ast = @pruned_asts[cfg_arch.name] + pruned_ast = @pruned_asts[design.name] return pruned_ast unless pruned_ast.nil? return nil unless @data.key?("operation()") - type_checked_ast = type_checked_operation_ast(cfg_arch.idl_compiler, global_symtab, effective_xlen) + type_checked_ast = type_checked_operation_ast(design.idl_compiler, global_symtab, effective_xlen) print "Pruning #{name} operation()..." pruned_ast = type_checked_ast.prune(fill_symtab(global_symtab, effective_xlen, type_checked_ast)) puts "done" pruned_ast.freeze_tree(global_symtab) - cfg_arch.idl_compiler.type_check( + design.idl_compiler.type_check( pruned_ast, fill_symtab(global_symtab, effective_xlen, pruned_ast), "#{name}.operation() (pruned)" ) - @pruned_asts[cfg_arch.name] = pruned_ast + @pruned_asts[design.name] = pruned_ast end # @param symtab [Idl::SymbolTable] Symbol table with global scope populated @@ -166,7 +166,7 @@ def reachable_functions(symtab, effective_xlen) [] else # RubyProf.start - ast = type_checked_operation_ast(symtab.cfg_arch.idl_compiler, symtab, effective_xlen) + ast = type_checked_operation_ast(symtab.design.idl_compiler, symtab, effective_xlen) print "Determining reachable funcs from #{name}..." fns = ast.reachable_functions(fill_symtab(symtab, effective_xlen, ast)) puts "done" @@ -186,7 +186,7 @@ def reachable_exceptions(symtab, effective_xlen) else # pruned_ast = pruned_operation_ast(symtab) # type_checked_operation_ast() - type_checked_ast = type_checked_operation_ast(symtab.cfg_arch.idl_compiler, symtab, effective_xlen) + type_checked_ast = type_checked_operation_ast(symtab.design.idl_compiler, symtab, effective_xlen) symtab = fill_symtab(symtab, effective_xlen, pruned_ast) type_checked_ast.reachable_exceptions(symtab) end @@ -214,7 +214,7 @@ def reachable_exceptions_str(symtab, effective_xlen=nil) else etype = symtab.get("ExceptionCode") if effective_xlen.nil? - if symtab.cfg_arch.multi_xlen? + if symtab.design.multi_xlen? if base.nil? ( pruned_ast = pruned_operation_ast(symtab, 32) @@ -241,7 +241,7 @@ def reachable_exceptions_str(symtab, effective_xlen=nil) e end else - effective_xlen = symtab.cfg_arch.mxlen + effective_xlen = symtab.design.mxlen pruned_ast = pruned_operation_ast(symtab, effective_xlen) print "Determining reachable exceptions from #{name}..." e = mask_to_array(pruned_ast.reachable_exceptions(fill_symtab(symtab, effective_xlen, pruned_ast))).map { |code| @@ -644,7 +644,7 @@ def operation_ast(symtab) return nil if @data["operation()"].nil? # now, parse the operation - @operation_ast = symtab.cfg_arch.idl_compiler.compile_inst_operation( + @operation_ast = symtab.design.idl_compiler.compile_inst_operation( self, symtab:, input_file: @data["$source"], @@ -719,7 +719,7 @@ def rv64? def excluded_by?(*args) return false if @data["excludedBy"].nil? - excluded_by = SchemaCondition.new(@data["excludedBy"], @cfg_arch) + excluded_by = SchemaCondition.new(@data["excludedBy"], @arch) ext_ver = if args.size == 1 @@ -730,7 +730,7 @@ def excluded_by?(*args) raise ArgumentError, "First parameter must be an extension name" unless args[0].respond_to?(:to_s) raise ArgumentError, "Second parameter must be an extension version" unless args[1].respond_to?(:to_s) - ExtensionVersion.new(args[0], args[1], @cfg_arch) + ExtensionVersion.new(args[0], args[1], @arch) end excluded_by.satisfied_by? do |r| @@ -738,19 +738,23 @@ def excluded_by?(*args) end end - # @param cfg_arch [ConfiguredArchitecture] The architecture definition - # @return [Boolean] whether or not the instruction is implemented given the supplies config options - def exists_in_cfg?(cfg_arch) - if cfg_arch.fully_configured? - (@data["base"].nil? || (cfg_arch.possible_xlens.include? @data["base"])) && - cfg_arch.implemented_extensions.any? { |e| defined_by?(e) } && - cfg_arch.implemented_extensions.none? { |e| excluded_by?(e) } + # @param design [Design] The design + # @return [Boolean] whether or not the instruction is implemented given the supplied design + # + # TODO: Does this function actually work for a partially configured design? + # It is calling DatabaseObject.defined_by? with ExtensionRequirement objects + # returned from Design.mandatory_ext_reqs() but only accepts an ExtensionVersion object. + def exists_in_design?(design) + if design.fully_configured? + (@data["base"].nil? || (design.possible_xlens.include?(@data["base"]))) && + design.implemented_ext_vers.any? { |ext_ver| defined_by?(ext_ver) } && + design.implemented_ext_vers.none? { |ext_ver| excluded_by?(ext_ver) } else - raise "unexpected cfg_arch type" unless cfg_arch.partially_configured? + raise "unexpected design type" unless design.partially_configured? - (@data["base"].nil? || (cfg_arch.possible_xlens.include? @data["base"])) && - cfg_arch.prohibited_extensions.none? { |e| defined_by?(e) } && - cfg_arch.mandatory_extensions.none? { |e| excluded_by?(e) } + (@data["base"].nil? || (design.possible_xlens.include?(@data["base"]))) && + design.prohibited_ext_reqs.none? { |ext_req| ext_req.satisfying_versions.any? { |ext_ver| defined_by?(ext_ver) } } + design.mandatory_ext_reqs.none? { |ext_req| excluded_by?(ext_req) } end end end diff --git a/lib/arch_obj_models/manual.rb b/lib/arch_obj_models/manual.rb index 396cb041d..29b67ec02 100644 --- a/lib/arch_obj_models/manual.rb +++ b/lib/arch_obj_models/manual.rb @@ -2,7 +2,7 @@ require "asciidoctor" -require_relative "obj" +require_relative "database_obj" class Manual < DatabaseObject def versions @@ -40,6 +40,9 @@ def name def title return @title unless @title.nil? + # See https://www.rubydoc.info/gems/asciidoctor for details on the Ruby API + # and https://www.rubydoc.info/gems/asciidoctor/Asciidoctor/Document for details on + # the Asciidoctor::Document object returned by Asciidoctor.load. @title = (Asciidoctor.load File.read(fullpath).scrub).doctitle.encode("US-ASCII") end @@ -61,8 +64,10 @@ class ManualVolume # @return [ManualVersion] The version this volume belongs to attr_reader :version - def cfg_arch = version.cfg_arch + def arch = version.arch + # @param data [Hash] Data from YAML file + # @param version [ManualVersion] def initialize(data, version) @data = data @version = version @@ -86,28 +91,28 @@ def chapter(name) = chapters.find { |c| c.name == name } def title = @data["title"] # @return [Array] Array of extension versions in this volume - def extensions - return @extensions unless @extensions.nil? + def ext_vers + return @ext_vers unless @ext_vers.nil? - @extensions = [] - return @extensions if @data["extensions"].nil? + @ext_vers = [] + return @ext_vers if @data["extensions"].nil? @data["extensions"].each do |ext| - ext_obj = cfg_arch.extension(ext[0]) + ext_obj = arch.extension(ext[0]) if ext_obj.nil? warn "Extension '#{ext[0]}' is not in the database" next end - ext_ver = ExtensionVersion.new(ext[0], ext[1], cfg_arch) + ext_ver = ExtensionVersion.new(ext[0], ext[1], arch) unless ext_obj.versions.any? { |known_ver| known_ver == ext_ver } warn "Extension '#{ext[0]}', version '#{ext[1]}' is not defined in the database" next end - @extensions << ext_ver + @ext_vers << ext_ver end - @extensions + @ext_vers end def repo_path=(path) @@ -158,11 +163,11 @@ def volumes def state = @data["state"] - # @return [Array] Array of extension versions in this manual version - def extensions - return @extensions unless @extensions.nil? + # @return [Array] Array of extension versions in this manual version across all volumes + def ext_vers + return @ext_vers unless @ext_vers.nil? - @extensions = volumes.map(&:extensions).flatten.uniq + @ext_vers = volumes.map(&:ext_vers).flatten.uniq end # @return [Array] All instructions defined in this version @@ -170,8 +175,8 @@ def instructions return @instructions unless @instructions.nil? @instructions = [] - extensions.each do |ext| - ext_obj = @cfg_arch.extension(ext.name) + ext_vers.each do |ext| + ext_obj = @arch.extension(ext.name) ext_obj.instructions.each do |inst| @instructions << inst end @@ -184,8 +189,8 @@ def csrs return @csrs unless @csrs.nil? @csrs = [] - extensions.each do |ext| - ext_obj = @cfg_arch.extension(ext.name) + ext_vers.each do |ext| + ext_obj = @arch.extension(ext.name) ext_obj.csrs.each do |csr| @csrs << csr end diff --git a/lib/arch_obj_models/parameter.rb b/lib/arch_obj_models/parameter.rb new file mode 100644 index 000000000..15eed6285 --- /dev/null +++ b/lib/arch_obj_models/parameter.rb @@ -0,0 +1,136 @@ +# frozen_string_literal: true + +require_relative "database_obj" +require_relative "schema" +require_relative "../version" + +# A parameter (AKA option, AKA implementation-defined value) supported by an extension +class Parameter + # @return [Architecture] The defining architecture + attr_reader :arch + + # @return [String] Parameter name + attr_reader :name + + # @return [String] Asciidoc description + attr_reader :desc + + # @return [Schema] JSON Schema for this param + attr_reader :schema + + # @return [String] Ruby code to perform validation above and beyond JSON schema + # @return [nil] If there is no extra validation + attr_reader :extra_validation + + # Some parameters are defined by multiple extensions (e.g., CACHE_BLOCK_SIZE by Zicbom and Zicboz). + # When defined in multiple places, the parameter *must* mean the exact same thing. + # + # @return [Array] The extension(s) that define this parameter + attr_reader :exts + + # @returns [Idl::Type] Type of the parameter + attr_reader :idl_type + + # Pretty convert extension schema to a string. + def schema_type + @schema.to_pretty_s + end + + # @param ext [Extension] + # @param name [String] + # @param data [Hash] List of all in-scope extensions that define this parameter. + # @return [String] Text to create a link to the parameter definition with the link text the parameter name. + # if only one extension defines the parameter, otherwise just the parameter name. + def name_potentially_with_link(in_scope_exts) + raise ArgumentError, "Expecting Array but got #{in_scope_exts.class}" unless in_scope_exts.is_a?(Array) + raise ArgumentError, "Expecting Array[Extension]" unless in_scope_exts[0].is_a?(Extension) + + if in_scope_exts.size == 1 + link_to_udb_doc_ext_param(in_scope_exts[0].name, name, name) + else + name + end + end + + # sorts by name + def <=>(other) + raise ArgumentError, "Parameters are only comparable to other extension parameters" unless other.is_a?(Parameter) + + @name <=> other.name + end +end + +class ParameterWithValue + # @return [Object] The parameter value + attr_reader :value + + # @return [String] Parameter name + def name = @param.name + + # @return [String] Asciidoc description + def desc = @param.desc + + # @return [Hash] JSON Schema for the parameter value + def schema = @param.schema + + # @return [String] Ruby code to perform validation above and beyond JSON schema + # @return [nil] If there is no extra validatino + def extra_validation = @param.extra_validation + + # @return [Extension] The extension that defines this parameter + def exts = @param.exts + + def initialize(param, value) + @param = param + @value = value + end +end diff --git a/lib/arch_obj_models/portfolio.rb b/lib/arch_obj_models/portfolio.rb index 5c5747f20..a5c3fc747 100644 --- a/lib/arch_obj_models/portfolio.rb +++ b/lib/arch_obj_models/portfolio.rb @@ -10,15 +10,16 @@ # A variable name with a "_data" suffix indicates it is the raw hash data from the portfolio YAML file. require "tmpdir" +require "forwardable" -require_relative "obj" +require_relative "database_obj" require_relative "schema" ################## # PortfolioClass # ################## -# Holds information from Portfolio class YAML file (certificate class or profile class). +# Holds information from Portfolio class YAML file (processor certificate class or profile class). # The inherited "data" member is the database of extensions, instructions, CSRs, etc. class PortfolioClass < DatabaseObject # @return [String] What kind of processor portfolio is this? @@ -42,17 +43,255 @@ def portfolio_classes_matching_portfolio_kind_and_processor_kind end end +################## +# PortfolioGroup # +################## + +# A portfolio group consists of a one or more profiles. +# Contains common code to aggregrate multiple portfolios for Profile Releases and PortfolioDesign classes. +# This not the base class for ProfileRelease but it does contain one of these. +# This is not a DatabaseObject. +class PortfolioGroup + extend Forwardable + + # Calls to these methods on PortfolioGroup are handled by the Array class. + # Avoids having to call portfolio_grp.portfolios. (just call portfolio_grp.). + def_delegators :@portfolios, :each, :map, :select + + # @param portfolios [Array] + def initialize(portfolios) + raise ArgumentError, "Need at least one portfolio" if portfolios.empty? + @portfolios = portfolios + end + + # @return [Array] All portfolios in this portfolio group + def portfolios = @portfolios + + # @return [Hash] Fully-constrained parameter values (those with just one possible value for this design). + def param_values + return @param_values unless @param_values.nil? + + @param_values = {} + portfolios.each do |portfolio| + @param_values.merge!(portfolio.all_in_scope_params.select(&:single_value?).map { |p| [p.name, p.value] }.to_h) + end + + @param_values + end + + # @return [Array] Sorted list of all extension requirements listed by the group. + def in_scope_ext_reqs + return @in_scope_ext_reqs unless @in_scope_ext_reqs.nil? + + @in_scope_ext_reqs = [] + portfolios.each do |portfolio| + @in_scope_ext_reqs += portfolio.in_scope_ext_reqs + end + + @in_scope_ext_reqs = @in_scope_ext_reqs.uniq(&:name).sort_by(&:name) + end + + # @return [Array] Sorted list of all mandatory extension requirements listed by the group. + def mandatory_ext_reqs + return @mandatory_ext_reqs unless @mandatory_ext_reqs.nil? + + @mandatory_ext_reqs = [] + portfolios.each do |portfolio| + @mandatory_ext_reqs += portfolio.mandatory_ext_reqs + end + + @mandatory_ext_reqs = @mandatory_ext_reqs.uniq(&:name).sort_by(&:name) + end + + # @return [Array] Sorted list of all optional extension requirements listed by the group. + def optional_ext_reqs + return @optional_ext_reqs unless @optional_ext_reqs.nil? + + @optional_ext_reqs = [] + portfolios.each do |portfolio| + @optional_ext_reqs += portfolio.optional_ext_reqs + end + + @optional_ext_reqs = @optional_ext_reqs.uniq(&:name).sort_by(&:name) + end + + # @return [Array] Sorted list of all mandatory or optional extensions referenced by the group. + def in_scope_extensions + return @in_scope_extensions unless @in_scope_extensions.nil? + + @in_scope_extensions = [] + portfolios.each do |portfolio| + @in_scope_extensions += portfolio.in_scope_extensions + end + + @in_scope_extensions = @in_scope_extensions.uniq(&:name).sort_by(&:name) + + end + + # @param design [Design] The design + # @return [Array] Sorted list of all instructions associated with extensions listed as + # mandatory or optional in portfolio. Uses instructions provided by the + # minimum version of the extension that meets the extension requirement. + def in_scope_instructions(design) + raise ArgumentError, "Require an IDesign object but got a #{design.class} object" unless design.is_a?(IDesign) + + return @in_scope_instructions unless @in_scope_instructions.nil? + + @in_scope_instructions = [] + portfolios.each do |portfolio| + @in_scope_instructions += portfolio.in_scope_instructions(design) + end + + @in_scope_instructions = + @in_scope_instructions.uniq(&:name).sort_by(&:name) + end + + # @param design [Design] The design + # @return [Array] Unsorted list of all CSRs associated with extensions listed as + # mandatory or optional in portfolio. Uses CSRs provided by the + # minimum version of the extension that meets the extension requirement. + def in_scope_csrs(design) + raise ArgumentError, "Require an IDesign object but got a #{design.class} object" unless design.is_a?(IDesign) + + return @in_scope_csrs unless @in_scope_csrs.nil? + + @in_scope_csrs = [] + portfolios.each do |portfolio| + @in_scope_csrs += portfolio.in_scope_csrs(design) + end + + @in_scope_csrs.uniq(&:name) + end + + # @param design [Design] The design + # @return [Array] Unsorted list of all in-scope exception codes. + def in_scope_exception_codes(design) + raise ArgumentError, "Require an IDesign object but got a #{design.class} object" unless design.is_a?(IDesign) + + return @in_scope_exception_codes unless @in_scope_exception_codes.nil? + + @in_scope_exception_codes = [] + portfolios.each do |portfolio| + @in_scope_exception_codes += portfolio.in_scope_exception_codes(design) + end + + @in_scope_exception_codes.uniq(&:name) + end + + # @param design [Design] The design + # @return [Array] Unsorted list of all in-scope interrupt codes. + def in_scope_interrupt_codes(design) + raise ArgumentError, "Require an IDesign object but got a #{design.class} object" unless design.is_a?(IDesign) + + return @in_scope_interrupt_codes unless @in_scope_interrupt_codes.nil? + + @in_scope_interrupt_codes = [] + portfolios.each do |portfolio| + @in_scope_interrupt_codes += portfolio.in_scope_interrupt_codes(design) + end + + @in_scope_interrupt_codes.uniq(&:name) + end + + # @return [String] Given an extension +ext_name+, return the presence as a string. + # Returns the greatest presence string across all profiles in the group. + # If the extension name isn't found in the release, return "-". + def extension_presence(ext_name) + greatest_presence = nil + + portfolios.each do |portfolio| + presence = portfolio.extension_presence_obj(ext_name) + + unless presence.nil? + if greatest_presence.nil? + greatest_presence = presence + elsif presence > greatest_presence + greatest_presence = presence + end + end + end + + greatest_presence.nil? ? "-" : greatest_presence.to_s_concise + end + + # @return [Array] Sorted list of parameters specified by any extension in portfolio. + def all_in_scope_params + @ret = [] + portfolios.each do |portfolio| + @ret += portfolio.all_in_scope_params + end + + @ret = @ret.uniq.sort + end + + # @param [ExtensionRequirement] + # @return [Array] Sorted list of extension parameters from portfolio for given extension. + def in_scope_params(ext_req) + @ret = [] + portfolios.each do |portfolio| + @ret += portfolio.in_scope_params(ext_req) + end + + @ret = @ret.uniq.sort + end + + # @return [Array] Sorted list of parameters out of scope across all in scope extensions. + def all_out_of_scope_params + @ret = [] + portfolios.each do |portfolio| + @ret += portfolio.all_out_of_scope_params + end + + @ret = @ret.uniq.sort + end + + # @param ext_name [String] Extension name + # @return [Array] Sorted list of parameters that are out of scope for named extension. + def out_of_scope_params(ext_name) + @ret = [] + portfolios.each do |portfolio| + @ret += portfolio.out_of_scope_params(ext_name) + end + + @ret = @ret.uniq.sort + end + + # @param param [Parameter] + # @return [Array] Sorted list of all in-scope extensions that define this parameter + # in the database and the parameter is in-scope. + def all_in_scope_exts_with_param(param) + @ret = [] + portfolios.each do |portfolio| + @ret += portfolio.all_in_scope_exts_with_param(param) + end + + @ret = @ret.uniq.sort + end + + # @param param [Parameter] + # @return [Array] List of all in-scope extensions that define this parameter in the + # database but the parameter is out-of-scope. + def all_in_scope_exts_without_param(param) + @ret = [] + portfolios.each do |portfolio| + @ret += portfolio.all_in_scope_exts_without_param(param) + end + + @ret = @ret.uniq.sort + end +end + ############# # Portfolio # ############# # Holds information about a Portfolio (certificate or profile). -# The inherited "data" member is the database of extensions, instructions, CSRs, etc. +# The inherited "data" member is YAML data from the architecture for this portfolio object. class Portfolio < DatabaseObject # @param obj_yaml [Hash] Contains contents of Portfolio yaml file (put in @data) # @param data_path [String] Path to yaml file # @param arch [Architecture] Entire database of RISC-V architecture standards - def initialize(obj_yaml, yaml_path, arch: nil) + def initialize(obj_yaml, yaml_path, arch) super # Calls parent class with same args I got end @@ -62,6 +301,9 @@ def introduction = @data["introduction"] # @return [String] Large enough to need its own heading (generally one level deeper than the "introduction"). def description = @data["description"] + # @return [Integer] 32 or 64 + def base = @data["base"] + # @return [Gem::Version] Semantic version of the Portfolio def version = Gem::Version.new(@data["version"]) @@ -128,7 +370,7 @@ def optional_ext_reqs = in_scope_ext_reqs(ExtensionPresence.optional) def optional_type_ext_reqs = in_scope_ext_reqs(ExtensionPresence.optional) # @param desired_presence [String, Hash, ExtensionPresence] - # @return [Array] - # Extensions with their portfolio information. + # @return [Array] Sorted list of extensions with their portfolio information. # If desired_presence is provided, only returns extensions with that presence. # If desired_presence is a String, only the presence portion of an ExtensionPresence is compared. def in_scope_ext_reqs(desired_presence = nil) @@ -170,11 +412,11 @@ def in_scope_ext_reqs(desired_presence = nil) in_scope_ext_reqs << if ext_data.key?("version") ExtensionRequirement.new( - ext_name, ext_data["version"], arch: @arch, + ext_name, ext_data["version"], @arch, presence: actual_presence_obj, note: ext_data["note"], req_id: "REQ-EXT-#{ext_name}") else ExtensionRequirement.new( - ext_name, arch: @arch, + ext_name, @arch, presence: actual_presence_obj, note: ext_data["note"], req_id: "REQ-EXT-#{ext_name}") end end @@ -183,31 +425,96 @@ def in_scope_ext_reqs(desired_presence = nil) raise "One or more extensions referenced by #{name} missing in database" if missing_ext - in_scope_ext_reqs + in_scope_ext_reqs.sort_by!(&:name) + end + + # @return [Array] Sorted list of all mandatory or optional extensions in portfolio. + # Each extension can have multiple versions (contains ExtensionVersion array). + def in_scope_extensions + return @in_scope_extensions unless @in_scope_extensions.nil? + + @in_scope_extensions = in_scope_ext_reqs.map do |ext_req| + ext_req.extension + end.reject(&:nil?) # Filter out extensions that don't exist yet. + + @in_scope_extensions.sort_by!(&:name) + end + + # @return [ExtensionVersion] List of all mandatory or optional extensions listed in portfolio. + # The minimum version of each extension that satisfies the extension requirements is provided. + def in_scope_min_satisfying_extension_versions + return @in_scope_min_satisfying_extension_versions unless @in_scope_min_satisfying_extension_versions.nil? + + @in_scope_min_satisfying_extension_versions = in_scope_ext_reqs.map do |ext_req| + ext_req.satisfying_versions.min + end.reject(&:nil?) # Filter out extensions that don't exist yet. + + @in_scope_min_satisfying_extension_versions end + # @param design [Design] The design # @return [Array] Sorted list of all instructions associated with extensions listed as - # mandatory or optional in portfolio. Uses minimum version of - # extension version that meets extension requirement specified in portfolio. - def in_scope_instructions + # mandatory or optional in portfolio. Uses instructions provided by the + # minimum version of the extension that meets the extension requirement. + def in_scope_instructions(design) + raise ArgumentError, "Require an IDesign object but got a #{design.class} object" unless design.is_a?(IDesign) + return @in_scope_instructions unless @in_scope_instructions.nil? - # XXX - # @in_scope_instructions = in_scope_ext_reqs.map { |ext_req| ext_req.instructions }.flatten.uniq.sort - @in_scope_instructions = in_scope_extensions.map { |ext| ext.instructions }.flatten.uniq.sort + @in_scope_instructions = + in_scope_min_satisfying_extension_versions.map {|ext_ver| ext_ver.in_scope_instructions(design) }.flatten.uniq.sort end - # @return [Array] List of all extensions listed in portfolio. - def in_scope_extensions - return @in_scope_extensions unless @in_scope_extensions.nil? + # @param design [Design] The design + # @return [Array] Unsorted list of all CSRs associated with extensions listed as + # mandatory or optional in portfolio. Uses CSRs provided by the + # minimum version of the extension that meets the extension requirement. + def in_scope_csrs(design) + raise ArgumentError, "Require an IDesign object but got a #{design.class} object" unless design.is_a?(IDesign) - @in_scope_extensions = in_scope_ext_reqs.map do |ext_req| - arch.extension(ext_req.name) - end.reject(&:nil?) # Filter out extensions that don't exist yet. + return @in_scope_csrs unless @in_scope_csrs.nil? + + @in_scope_csrs = + in_scope_min_satisfying_extension_versions.map {|ext_ver| ext_ver.in_scope_csrs(design) }.flatten.uniq + end - @in_scope_extensions + # @param design [Design] The design + # @return [Array] Unsorted list of all in-scope exception codes. + # TODO: See https://github.com/riscv-software-src/riscv-unified-db/issues/291 + # TODO: Still needs work and haven't created in_scope_interrupt_codes yet. + # TODO: Extensions should provide conditional information ("when" statements?) + # that we evaluate here to determine if a particular exception code can + # actually be generated in a design. + # Also, probably shouldn't be calling "ext?" since that doesn't the in_scope lists of extensions. + def in_scope_exception_codes(design) + raise ArgumentError, "Require an IDesign object but got a #{design.class} object" unless design.is_a?(IDesign) + + return @in_scope_exception_codes unless @in_scope_exception_codes.nil? + + @in_scope_exception_codes = + in_scope_min_satisfying_extension_versions.reduce([]) do |list, ext_version| + ecodes = ext_version.ext["exception_codes"] + next list if ecodes.nil? + + ecodes.each do |ecode| + # Require all exception codes be unique in a given portfolio. + 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 design.ext?(ext_version.name.to_sym, ecode["when"]["version"]) + end + list << ExceptionCode.new(ecode["name"], ecode["var"], ecode["num"], arch) + end + list + end end + # @param design [Design] The design + # @return [Array] Unsorted list of all in-scope interrupt codes. + # TODO: Actually implement this to use Design. See in_scope_exception_codes() above. + def in_scope_interrupt_codes(design) = arch.interrupt_codes + # @return [Boolean] Does the profile differentiate between different types of optional. def uses_optional_types? return @uses_optional_types unless @uses_optional_types.nil? @@ -225,50 +532,48 @@ def uses_optional_types? @uses_optional_types end - # Called by rakefile when generating a portfolio. - # Creates an in-memory data structure used by all portfolio routines that access a cfg_arch. - # - # @return [ConfiguredArchitecture] A partially-configured architecture definition corresponding to this portfolio. - def to_cfg_arch - return @generated_cfg_arch unless @generated_cfg_arch.nil? - - # build up a config for the portfolio - config_data = { - "$schema" => "config_schema.json", - "type" => "partially configured", - "kind" => "architecture configuration", - "name" => name, - "description" => "A partially configured architecture definition corresponding to the #{name} portfolio.", - "mandatory_extensions" => mandatory_ext_reqs.map do |ext_req| - { - "name" => ext_req.name, - "version" => ext_req.requirement_specs.map(&:to_s) - } - end, - "params" => all_in_scope_ext_params.select(&:single_value?).map { |p| [p.name, p.value] }.to_h - } - - # TODO: Add list of prohibited_extensions - - @generated_cfg_arch = - Dir.mktmpdir do |dir| - FileUtils.mkdir("#{dir}/#{name}") - File.write("#{dir}/#{name}/cfg.yaml", YAML.safe_dump(config_data, permitted_classes: [Date])) - @generated_cfg_arch = ConfiguredArchitecture.new(name, @arch.path, cfg_path: dir) - end - end + ########################################################################### + # Portfolio types that supported the concept of in-scope and out-of-scope # + # parameter have to override the following methods. # + ########################################################################### + + # @return [Array] List of parameters specified by any extension in portfolio. + def all_in_scope_params = [] + + # @param [ExtensionRequirement] + # @return [Array] Sorted list of extension parameters from portfolio for given extension. + def in_scope_params(ext_req) = [] + + # @return [Array] Sorted list of parameters out of scope across all in scope extensions. + def all_out_of_scope_params = [] + + # @param ext_name [String] Extension name + # @return [Array] Sorted list of parameters that are out of scope for named extension. + def out_of_scope_params(ext_name) = [] + + # @param param [Parameter] + # @return [Array] Sorted list of all in-scope extensions that define this parameter + # in the database and the parameter is in-scope. + def all_in_scope_exts_with_param(param) = [] - ################################### - # InScopeExtensionParameter Class # - ################################### + # @param param [Parameter] + # @return [Array] List of all in-scope extensions that define this parameter in the + # database but the parameter is out-of-scope. + def all_in_scope_exts_without_param(param) = [] - # Holds extension parameter information from the portfolio. - class InScopeExtensionParameter - attr_reader :param # ExtensionParameter object (from the architecture database) + ########################## + # InScopeParameter Class # + ########################## + + class InScopeParameter + # @return [Parameter] Parameter object (from the architecture database) + attr_reader :param + + # @return [String] Optional note associated with the parameter attr_reader :note def initialize(param, schema_hash, note) - raise ArgumentError, "Expecting ExtensionParameter" unless param.is_a?(ExtensionParameter) + raise ArgumentError, "Expecting Parameter" unless param.is_a?(Parameter) if schema_hash.nil? schema_hash = {} @@ -309,165 +614,10 @@ def allowed_values # sorts by name def <=>(other) raise ArgumentError, - "InScopeExtensionParameter are only comparable to other parameter constraints" unless other.is_a?(InScopeExtensionParameter) + "InScopeParameter are only comparable to other parameter constraints" unless other.is_a?(InScopeParameter) @param.name <=> other.param.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_name, ext_data| - next if ext_name[0] == "$" - - # Find Extension object from database - ext = @arch.extension(ext_name) - raise "Cannot find extension named #{ext_name}" if ext.nil? - - ext_data["parameters"]&.each do |param_name, param_data| - param = ext.params.find { |p| p.name == param_name } - raise "There is no param '#{param_name}' in extension '#{ext_name}" if param.nil? - - next unless ext.versions.any? do |ext_ver| - ver_req = ext_data["version"] || ">= #{ext.min_version.version_spec}" - ExtensionRequirement.new(ext_name, ver_req, arch: @arch).satisfied_by?(ext_ver) && - param.defined_in_extension_version?(ext_ver) - end - - @all_in_scope_ext_params << - InScopeExtensionParameter.new(param, 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_data = @data["extensions"][ext_req.name] - raise "Cannot find extension named #{ext_req.name}" if ext_data.nil? - - # Find Extension object from database - ext = @arch.extension(ext_req.name) - raise "Cannot find extension named #{ext_req.name}" if ext.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_data["parameters"]&.each do |param_name, param_data| - # Find ExtensionParameter object from database - ext_param = ext.params.find { |p| p.name == param_name } - raise "There is no param '#{param_name}' in extension '#{ext_req.name}" if ext_param.nil? - - next unless ext.versions.any? do |ext_ver| - ext_req.satisfied_by?(ext_ver) && - ext_param.defined_in_extension_version?(ext_ver) - end - - ext_params << - InScopeExtensionParameter.new(ext_param, 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 = @arch.extension(ext_req.name) - ext.params.each do |param| - next if all_in_scope_ext_params.any? { |c| c.param.name == param.name } - - next unless ext.versions.any? do |ext_ver| - ext_req.satisfied_by?(ext_ver) && - param.defined_in_extension_version?(ext_ver) - end - - @all_out_of_scope_params << param - 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| param.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) - raise ArgumentError, "Expecting ExtensionParameter" unless param.is_a?(ExtensionParameter) - - exts = [] - - # Iterate through all the extensions in the architecture database that define this parameter. - param.exts.each do |ext| - found = false - - in_scope_extensions.each do |in_scope_ext| - if ext.name == in_scope_ext.name - found = true - next - end - end - - if found - # Only add extensions that exist in this portfolio. - exts << ext - 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) - raise ArgumentError, "Expecting ExtensionParameter" unless param.is_a?(ExtensionParameter) - - exts = [] # Local variable, no caching - - # Iterate through all the extensions in the architecture database that define this parameter. - param.exts.each do |ext| - found = false - - in_scope_extensions.each do |in_scope_ext| - if ext.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 - end - end - - # Return intersection of extension names - exts - end + end # class InScopeParameter ############################ # RevisionHistory Subclass # diff --git a/lib/arch_obj_models/profile.rb b/lib/arch_obj_models/profile.rb index 05acb3433..d74c22dad 100644 --- a/lib/arch_obj_models/profile.rb +++ b/lib/arch_obj_models/profile.rb @@ -25,7 +25,7 @@ def doc_license def profile_releases return @profile_releases unless @profile_releases.nil? - @profile_releases = @cfg_arch.profile_releases.select { |pr| pr.profile_class.name == name } + @profile_releases = @arch.profile_releases.select { |pr| pr.profile_class.name == name } @profile_releases end @@ -37,7 +37,7 @@ def profile_releases_matching_processor_kind matching_classes = portfolio_classes_matching_portfolio_kind_and_processor_kind # Look for all profile releases that are from any of the matching classes. - @profile_releases_matching_processor_kind = @cfg_arch.profile_releases.select { |pr| + @profile_releases_matching_processor_kind = @arch.profile_releases.select { |pr| matching_classes.any? { |matching_class| matching_class.name == pr.profile_class.name } } @@ -48,48 +48,46 @@ def profile_releases_matching_processor_kind def profiles return @profiles unless @profiles.nil? - @profiles = @cfg_arch.profiles.select {|profile| profile.profile_class.name == name} + @profiles = @arch.profiles.select {|profile| profile.profile_class.name == name} end # @return [Array] All profiles in database matching my processor kind def profiles_matching_processor_kind return @profiles_matching_processor_kind unless @profiles_matching_processor_kind.nil? - @profiles_matching_processor_kind = @cfg_arch.profiles.select {|profile| profile.profile_class.processor_kind == processor_kind} + @profiles_matching_processor_kind = @arch.profiles.select {|profile| profile.profile_class.processor_kind == processor_kind} end - # @return [Array] List of all extensions referenced by the profile class - def referenced_extensions - return @referenced_extensions unless @referenced_extensions.nil? + # @return [Array] Sorted list of all mandatory or optional extensions across the profile releases belonging + # to the profile class + def in_scope_extensions + return @in_scope_extensions unless @in_scope_extensions.nil? - @referenced_extensions = [] + @in_scope_extensions = [] profiles.each do |profile| - @referenced_extensions += profile.in_scope_extensions + @in_scope_extensions += profile.in_scope_extensions end - @referenced_extensions.uniq!(&:name) - - @referenced_extensions + @in_scope_extensions = @in_scope_extensions.uniq(&:name).sort_by(&:name) end - # @return [Array] List of all extensions referenced by any profile class in the database with my processor kind - def referenced_extensions_matching_processor_kind - return @reference_extensions_matching_processor_kind unless @reference_extensions_matching_processor_kind.nil? + # @return [Array] Sorted list of all potential extensions with my processor kind + def in_scope_extensions_matching_processor_kind + return @in_scope_extensions_matching_processor_kind unless @in_scope_extensions_matching_processor_kind.nil? - @reference_extensions_matching_processor_kind = [] + @in_scope_extensions_matching_processor_kind = [] profiles_matching_processor_kind.each do |profile| - @reference_extensions_matching_processor_kind += profile.in_scope_extensions + @in_scope_extensions_matching_processor_kind += profile.in_scope_extensions end - @reference_extensions_matching_processor_kind.uniq!(&:name) - - @reference_extensions_matching_processor_kind + @in_scope_extensions_matching_processor_kind = + @in_scope_extensions_matching_processor_kind.uniq(&:name).sort_by(&:name) end end # A profile release consists of a number of releases each with one or more profiles. # For example, the RVA20 profile release has profiles RVA20U64 and RVA20S64. -# Note there is no Portfolio* base class for a ProfileRelease to inherit from since there is no +# Note there is no Portfolio base class for a ProfileRelease to inherit from since there is no # equivalent to a ProfileRelease in a Certificate so no potential for a shared base class. class ProfileRelease < DatabaseObject def marketing_name = @data["marketing_name"] @@ -116,7 +114,7 @@ def contributors # @return [ProfileClass] Profile Class that this ProfileRelease belongs to def profile_class - profile_class = @cfg_arch.profile_class(@data["class"]) + profile_class = @arch.ref(@data["class"]['$ref']) raise "No profile class named '#{@data["class"]}'" if profile_class.nil? profile_class @@ -128,45 +126,29 @@ def profiles @profiles = [] @data["profiles"].each do |profile_ref| - @profiles << @cfg_arch.ref(profile_ref["$ref"]) + @profiles << @arch.ref(profile_ref["$ref"]) end @profiles end - # @return [Array] List of all extensions referenced by the release - def referenced_extensions - return @referenced_extensions unless @referenced_extensions.nil? + # @return [PortfolioGroup] All portfolios in this profile release + def portfolio_grp + return @portfolio_grp unless @portfolio_grp.nil? - @referenced_extensions = [] - profiles.each do |profile| - @referenced_extensions += profile.in_scope_extensions - end + @portfolio_grp = PortfolioGroup.new(profiles) + end - @referenced_extensions.uniq!(&:name) + ##################################### + # METHODS HANDLED BY PortfolioGroup # + ##################################### - @referenced_extensions - end + # @return [Array] List of all mandatory or optional extensions referenced by this profile release. + def in_scope_extensions = portfolio_grp.in_scope_extensions # @return [String] Given an extension +ext_name+, return the presence as a string. # Returns the greatest presence string across all profiles in the release. # If the extension name isn't found in the release, return "-". - def extension_presence(ext_name) - greatest_presence = nil - - profiles.each do |profile| - presence = profile.extension_presence_obj(ext_name) - - unless presence.nil? - if greatest_presence.nil? - greatest_presence = presence - elsif presence > greatest_presence - greatest_presence = presence - end - end - end - - greatest_presence.nil? ? "-" : greatest_presence.to_s_concise - end + def extension_presence(ext_name) = portfolio_grp.extension_presence(ext_name) end # Representation of a specific profile in a profile release. @@ -176,7 +158,7 @@ def marketing_name = @data["marketing_name"] # @return [ProfileRelease] The profile release this profile belongs to def profile_release - profile_release = @cfg_arch.ref(@data["release"]["$ref"]) + profile_release = @arch.ref(@data["release"]["$ref"]) raise "No profile release named '#{@data["release"]["$ref"]}'" if profile_release.nil? profile_release @@ -195,9 +177,6 @@ def base @data["base"] end - # @return [Array] List of all extensions referenced by the profile - def referenced_extensions = in_scope_extensions - # Too complicated to put in profile ERB template. # @param presence_type [String] # @param heading_level [Integer] @@ -255,7 +234,7 @@ def extensions_to_adoc(presence_type, heading_level) def ext_req_to_adoc(ext_req) ret = [] - ext = cfg_arch.extension(ext_req.name) + ext = arch.extension(ext_req.name) ret << "* *#{ext_req.name}* " + (ext.nil? ? "" : ext.long_name) ret << "+" ret << "Version #{ext_req.requirement_specs_to_s}" diff --git a/lib/architecture.rb b/lib/architecture.rb index 0ff77ba80..fbfc3b030 100644 --- a/lib/architecture.rb +++ b/lib/architecture.rb @@ -1,10 +1,12 @@ # frozen_string_literal: true -# Contains the "database" of RISC-V standards including extensions, instructions, -# CSRs, Profiles, and Certificates. Could be either the standard spec (defined by RISC-V International) -# of a custom spec (defined as an arch_overlay in /cfgs dir). +# The Architecture class is the API to the architecture database. +# The "database" contains RISC-V standards including extensions, instructions, +# CSRs, Profiles, and Certificates. +# The Architecture class is used by backends to export the information in the +# architecture database to create various outputs. # -# Creates Ruby functions at runtime (see generate_obj_methods() and OBJS array). +# The Architecture class creates Ruby functions at runtime (see generate_obj_methods() and OBJS array). # 1) Function to return Array (every klass in database) # 2) Function to return Hash (hash entry is nil if name doesn't exist) # 3) Function to return Klass given name (nil if name doesn't exist) @@ -14,8 +16,8 @@ # Extension extensions() extension_hash() extension(name) # Instruction instructions() instruction_hash() instruction(name) # Csr csrs() csr_hash() csr(name) -# CertClass cert_classes() cert_class_hash() cert_class(name) -# CertModel cert_models() cert_model_hash() cert_model(name) +# ProcCertClass proc_cert_classes() proc_cert_class_hash() proc_cert_class(name) +# ProcCertModel proc_cert_models() proc_cert_model_hash() proc_cert_model(name) # ProfileClass profile_classes() profile_class_hash() profile_class(name) # ProfileRelease profile_releases() profile_release_hash() profile_release(name) # Profile profiles() profile_hash() profile(name) @@ -26,7 +28,7 @@ # # klass Array Hash Klass func(String name) # ================== ================== ======================= ========================= -# ExtensionParameter params() param_hash() param(name) +# Parameter params() param_hash() param(name) # PortfolioClass portfolio_classes() portfolio_class_hash() portfolio_class(name) # Portfolio portfolios() portfolio_hash() portfolio(name) # ExceptionCodes exception_codes() @@ -39,8 +41,6 @@ require "pathname" require "yaml" -require_relative "idl" - require_relative "arch_obj_models/certificate" require_relative "arch_obj_models/csr" require_relative "arch_obj_models/csr_field" @@ -52,13 +52,21 @@ require_relative "arch_obj_models/profile" class Architecture - # @return [Pathname] Path to the directory with the standard YAML files + # @return [String] Best name to identify architecture + attr_reader :name + + # @return [Pathname] Path to the directory containing YAML files defining the RISC-V standards attr_reader :path - # @param arch_dir [String,Pathname] Path to a directory with a fully merged/resolved architecture definition - def initialize(arch_dir) + # Initialize a new architecture definition + # + # @param name [#to_s] The name associated with this architecture + # @param arch_dir [String, Pathname] Path to a directory with a fully merged/resolved architecture definition + def initialize(name, arch_dir) + @name = name.to_s.freeze + @arch_dir = Pathname.new(arch_dir) - raise "Arch directory not found: #{arch_dir}" unless @arch_dir.exist? + raise "Architecture directory #{arch_dir} not found" unless @arch_dir.exist? @arch_dir = @arch_dir.realpath @path = @arch_dir # alias @@ -77,6 +85,11 @@ def validate(show_progress: true) end end + # These instance methods are create when this Architecture class is first loaded. + # This is a Ruby "class" method and so self is the entire Architecture class, not an instance it. + # However, this class method creates normal instance methods and when they are called + # self is an instance of the Architecture class. + # # @!macro [attach] generate_obj_methods # @method $1s # @return [Array<$3>] List of all $1s defined in the standard @@ -98,7 +111,7 @@ def self.generate_obj_methods(fn_name, arch_dir, obj_class) @object_hashes[arch_dir] = {} Dir.glob(@arch_dir / arch_dir / "**" / "*.yaml") do |obj_path| obj_yaml = YAML.load_file(obj_path, permitted_classes: [Date]) - @objects[arch_dir] << obj_class.new(obj_yaml, Pathname.new(obj_path).realpath, arch: self) + @objects[arch_dir] << obj_class.new(obj_yaml, Pathname.new(obj_path).realpath, self) @object_hashes[arch_dir][@objects[arch_dir].last.name] = @objects[arch_dir].last end @objects[arch_dir] @@ -138,14 +151,14 @@ def self.generate_obj_methods(fn_name, arch_dir, obj_class) klass: Csr }, { - fn_name: "cert_class", - arch_dir: "certificate_class", - klass: CertClass + fn_name: "proc_cert_class", + arch_dir: "proc_cert_class", + klass: ProcCertClass }, { - fn_name: "cert_model", - arch_dir: "certificate_model", - klass: CertModel + fn_name: "proc_cert_model", + arch_dir: "proc_cert_model", + klass: ProcCertModel }, { fn_name: "manual", @@ -189,14 +202,14 @@ def objs @objs.freeze end - # @return [Array] Alphabetical list of all parameters defined in the architecture + # @return [Array] Alphabetical list of all parameters defined in the architecture def params return @params unless @params.nil? @params = extensions.map(&:params).flatten.uniq(&:name).sort_by!(&:name) end - # @return [Hash] Hash of all extension parameters defined in the architecture + # @return [Hash] Hash of all extension parameters defined in the architecture def param_hash return @param_hash unless @param_hash.nil? @@ -207,7 +220,7 @@ def param_hash @param_hash end - # @return [ExtensionParameter] Parameter named +name+ + # @return [Parameter] Parameter named +name+ # @return [nil] if there is no parameter named +name+ def param(name) param_hash[name] @@ -217,7 +230,7 @@ def param(name) def portfolio_classes return @portfolio_classes unless @portfolio_classes.nil? - @portfolio_classes = profile_classes.concat(cert_classes).sort_by!(&:name) + @portfolio_classes = profile_classes.concat(proc_cert_classes).sort_by!(&:name) end # @return [Hash] Hash of all portfolio classes defined in the architecture @@ -309,12 +322,12 @@ def ref(uri) file_path, obj_path = uri.split("#") obj = case file_path - when /^certificate_class.*/ - cert_class_name = File.basename(file_path, ".yaml") - cert_class(cert_class_name) - when /^certificate_model.*/ - cert_model_name = File.basename(file_path, ".yaml") - cert_model(cert_model_name) + when /^proc_cert_class.*/ + proc_cert_class_name = File.basename(file_path, ".yaml") + proc_cert_class(proc_cert_class_name) + when /^proc_cert_model.*/ + proc_cert_model_name = File.basename(file_path, ".yaml") + proc_cert_model(proc_cert_model_name) when /^csr.*/ csr_name = File.basename(file_path, ".yaml") csr(csr_name) diff --git a/lib/backend_helpers.rb b/lib/backend_helpers.rb new file mode 100644 index 000000000..e2cf35b51 --- /dev/null +++ b/lib/backend_helpers.rb @@ -0,0 +1,312 @@ +# frozen_string_literal: true +# +# Collection of "helper" functions that can be called from backends and/or ERB templates. + +require "erb" +require "pathname" + +# Add to standard String class. +class String + # Should be called on all RISC-V extension, instruction, CSR, and CSR field names. + # Parameters never have periods in their names so they don't need to be sanitized. + # + # @param name [String] Some RISC-V name which might have periods in it or ampersand + # @return [String] New String with periods replaced with underscores and ampersands replaced with "_and_" + def sanitize = String.new(self).gsub(".", "_").gsub("&", "_and_") +end + +# This module is included in the Design class so its methods are available to be called directly +# without having to prefix a method with the module name. +module TemplateHelpers + # Include a partial ERB template into a full ERB template. + # + # @param template_pname [String] Path to template file relative to backends directory + # @param inputs [Hash] Input objects to pass into template + # @return [String] Result of ERB evaluation of the template file + def partial(template_pname, inputs = {}) + template_path = Pathname.new($root / "backends" / template_pname) + raise ArgumentError, "Template '#{template_path} not found" unless template_path.exist? + + erb = ERB.new(template_path.read, trim_mode: "-") + erb.filename = template_path.realpath.to_s + + erb.result(OpenStruct.new(inputs).instance_eval { binding }) + end + + ######### + # LINKS # + ######### + + # Links are created with this proprietary format so that they can be converted + # later into either AsciiDoc or Antora links (see the two implementations of "resolve_links"). + # %%UDB_DOC_LINK%;;%% + # + # Documentation: + # - How to make cross-references: https://docs.asciidoctor.org/asciidoc/latest/macros/xref/ + # - How to create anchors: https://docs.asciidoctor.org/asciidoc/latest/attributes/id/ + # - See https://github.com/riscv/riscv-isa-manual/issues/1397#issuecomment-2515109936 for + # discussion about using [#anchor] instead of [[anchor]] due to Antora's support. + + # @return [String] A hyperlink to UDB extension documentation + # @param ext_name [String] Name of the extension + def link_to_udb_doc_ext(ext_name) + "%%UDB_DOC_LINK%ext;#{ext_name.sanitize};#{ext_name}%%" + end + + # @return [String] A hyperlink to UDB parameter documentation + # @param ext_name [String] Name of the extension + # @param param_name [String] Name of the parameter + # @param link_text [String] What to put in the link text (don't assume param_name) + def link_to_udb_doc_ext_param(ext_name, param_name, link_text) + check_no_periods(param_name) + "%%UDB_DOC_LINK%ext_param;#{ext_name.sanitize}.#{param_name};#{link_text}%%" + end + + # @return [String] A hyperlink to UDB instruction documentation + # @param inst_name [String] Name of the instruction + def link_to_udb_doc_inst(inst_name) + "%%UDB_DOC_LINK%inst;#{inst_name.sanitize};#{inst_name}%%" + end + + # @return [String] A hyperlink to UDB CSR documentation + # @param csr_name [String] Name of the CSR + def link_to_udb_doc_csr(csr_name) + "%%UDB_DOC_LINK%csr;#{csr_name.sanitize};#{csr_name}%%" + end + + # @return [String] A hyperlink to UDB CSR field documentation + # @param csr_name [String] Name of the CSR + # @param field_name [String] Name of the CSR field + def link_to_udb_doc_csr_field(csr_name, field_name) + "%%UDB_DOC_LINK%csr_field;#{csr_name.sanitize}.#{field_name.sanitize};#{csr_name}.#{field_name}%%" + end + + # @return [String] A hyperlink to UDB IDL function documentation + # @param func_name [String] Name of the IDL function + def link_to_udb_doc_idl_func(func_name) + "%%UDB_DOC_LINK%func;#{func_name.sanitize};#{func_name}%%" + end + + # @return [String] A hyperlink to a UDB certification coverage point (separate chapters for cov pts and test procs) + # @param org [String] Organization of coverage points and test procedures (sep=separate chapters, combo=combined chapters, appendix=appendix) + # @param id [String] ID of the coverage point + def link_to_udb_doc_cov_pt(org, id) + raise ArgumentError, "Unknown org value of '#{org}' for ID '#{id}'" unless org == "sep" || org == "combo" || org == "appendix" + "%%UDB_DOC_COV_PT_LINK%#{org};#{id.sanitize};#{id}%%" + end + + # @return [String] A hyperlink into IDL instruction code + # @param func_name [String] Name of the instruction + # @param id [String] ID within the instruction code + def link_into_idl_inst_code(inst_name, id) + "%%IDL_CODE_LINK%inst;#{inst_name.sanitize}.#{id.sanitize};#{inst_name}.#{id}%%" + end + # TODO: Add csr and csr_field support + + ########### + # ANCHORS # + ########### + + # @return [String] An anchor for UDB extension documentation + # @param ext_name [String] Name of the extension + def anchor_for_udb_doc_ext(ext_name) + "[#udb:doc:ext:#{ext_name.sanitize}]" + end + + # @return [String] An anchor for UDB parameter documentation + # @param ext_name [String] Name of the extension + # @param param_name [String] Name of the parameter + def anchor_for_udb_doc_ext_param(ext_name, param_name) + check_no_periods(param_name) + "[#udb:doc:ext_param:#{ext_name.sanitize}:#{param_name}]" + end + + # @return [String] An anchor for UDB instruction documentation + # @param name [String] Name of the instruction + def anchor_for_udb_doc_inst(name) + "[#udb:doc:inst:#{name.sanitize}]" + end + + # @return [String] An anchor for UDB CSR documentation + # @param name [String] Name of the CSR + def anchor_for_udb_doc_csr(name) + "[#udb:doc:csr:#{name.sanitize}]" + end + + # @return [String] An anchor for UDB CSR field documentation + # @param csr_name [String] Name of the CSR + # @param field_name [String] Name of the CSR field + def anchor_for_udb_doc_csr_field(csr_name, field_name) + "[#udb:doc:csr_field:#{csr_name.sanitize}:#{field_name.sanitize}]" + end + + # @return [String] An anchor for an IDL function documentation + # @param name [String] Name of the function + def anchor_for_udb_doc_idl_func(name) + "[#udb:doc:func:#{name.sanitize}]" + end + + # @return [String] An anchor for a UDB coverage point documentation + # @param org [String] Document organization of coverage points and test procedures (sep=separate chapters, combo=combined chapters, appendix=appendix) + # @param id [String] ID of the coverage point + # Have to use [[anchor]] instead of [#anchor] since only the former works when in a table cell. + def anchor_for_udb_doc_cov_pt(org, id) + raise ArgumentError, "Unknown org value of '#{org}' for ID '#{id}'" unless org == "sep" || org == "combo" || org == "appendix" + "[[udb:doc:cov_pt:#{org}:#{id.sanitize}]]" + end + + # @return [String] An anchor inside IDL instruction code + # @param func_name [String] Name of the instruction + # @param id [String] ID within the instruction code + def anchor_inside_idl_inst_code(inst_name, id) + "[#idl:code:inst:#{inst_name.sanitize}:#{id.sanitize}]" + end + # TODO: Add csr and csr_field support + + private + #@ param s [String] + def check_no_periods(s) + raise ArgumentError, "Periods are not allowed in '#{s}'" if s.include?(".") + end +end + +# Utilities for a backend to generate AsciiDoc. +module AsciidocUtils + # The syntax "class << self" causes all methods to be treated as class methods. + class << self + # Convert proprietary link format to legal AsciiDoc links. + # They are converted to AsciiDoc internal cross references (i.e., <>). + # For example, + # %%UDB_DOC_LINK%inst;add;add instruction%% + # is converted to: + # <> + # + # @param path_or_str [Pathname or String] + # @return [String] + def resolve_links(path_or_str) + str = + if path_or_str.is_a?(Pathname) + path_or_str.read + else + path_or_str + end + str.gsub(/%%UDB_DOC_LINK%([^;%]+)\s*;\s*([^;%]+)\s*;\s*([^%]+)%%/) do + type = Regexp.last_match[1] + name = Regexp.last_match[2] + link_text = Regexp.last_match[3] + + case type + when "ext" + "<>" + when "ext_param" + ext_name, param_name = name.split('.') + "<>" + when "inst" + "<>" + when "csr" + "<>" + when "csr_field" + csr_name, field_name = name.split('.') + "<>" + when "func" + "<>" + else + raise "Unhandled link type of '#{type}' for '#{name}' with link_text '#{link_text}'" + end + end.gsub(/%%UDB_DOC_COV_PT_LINK%([^;%]+)\s*;\s*([^;%]+)\s*;\s*([^%]+)%%/) do + org = Regexp.last_match[1] # "sep", "combo", or "appendix" + id = Regexp.last_match[2] + link_text = Regexp.last_match[3] + + raise "Unhandled link org of '#{org}' for ID '#{id}' with link_text '#{link_text}'" unless org == "sep" || org == "combo" || org == "appendix" + + "<>" + end.gsub(/%%IDL_CODE_LINK%([^;%]+)\s*;\s*([^;%]+)\s*;\s*([^%]+)%%/) do + type = Regexp.last_match[1] + name = Regexp.last_match[2] + link_text = Regexp.last_match[3] + + case type + when "inst" + inst_name, id = name.split('.') + "<>" + # TODO: Add csr and csr_field support + else + raise "Unhandled link type of '#{type}' for '#{name}' with link_text '#{link_text}'" + end + end + end + end +end + +# Utilities for a backend to generate an Antora web-site. +module AntoraUtils + # The syntax "class << self" causes all methods to be treated as class methods. + class << self + # Convert proprietary link format to legal AsciiDoc links. + # + # They are converted to AsciiDoc external cross references in the form: + # xref::.adoc:#[]) + # where <> don't appear in the actual cross reference (just there to indicate variable content). + # + # For example, + # %%UDB_DOC_LINK%inst;add;add instruction%% + # is converted to: + # xref:insts:add.adoc#udb:doc:add[add instruction] + # + # Antora supports the module name after the "xref:". In the example above, it the module name is "insts" + # and corresponds to the directory name the add.adoc file is located in. For more details, see: + # https://docs.antora.org/antora/latest/page/xref/ + # and then + # https://docs.antora.org/antora/latest/page/resource-id-coordinates/ + # + # @param path_or_str [Pathname or String] + # @return [String] + def resolve_links(path_or_str) + str = + if path_or_str.is_a?(Pathname) + path_or_str.read + else + path_or_str + end + str.gsub(/%%UDB_DOC_LINK%([^;%]+)\s*;\s*([^;%]+)\s*;\s*([^%]+)%%/) do + type = Regexp.last_match[1] + name = Regexp.last_match[2] + link_text = Regexp.last_match[3] + + case type + when "ext" + "xref:exts:#{name}.adoc#udb:doc:ext:#{name}[#{link_text}]" + when "ext_param" + ext_name, param_name = name.split('.') + "xref:exts:#{ext_name}.adoc#udb:doc:ext_param:#{ext_name}:#{param_name}[#{link_text}]" + when "inst" + "xref:insts:#{name}.adoc#udb:doc:inst:#{name}[#{link_text}]" + when "csr" + "xref:csrs:#{name}.adoc#udb:doc:csr:#{name}[#{link_text}]" + when "csr_field" + csr_name, field_name = name.split('.') + "xref:csrs:#{csr_name}.adoc#udb:doc:csr_field:#{csr_name}:#{field_name}[#{link_text}]" + when "func" + # All functions are in the same file called "funcs.adoc". + "xref:funcs:funcs.adoc#udb:doc:func:#{name}[#{link_text.gsub(']', '\]')}]" + else + raise "Unhandled link type of '#{type}' for '#{name}' with link_text '#{link_text}'" + end + end.gsub(/%%IDL_CODE_LINK%([^;%]+)\s*;\s*([^;%]+)\s*;\s*([^%]+)%%/) do + type = Regexp.last_match[1] + name = Regexp.last_match[2] + link_text = Regexp.last_match[3] + + case type + when "inst" + inst_name, id = name.split('.') + "xref:insts:#{inst_name}.adoc#idl:code:inst:#{inst_name}:#{id}[#{link_text}]" + # TODO: Add csr and csr_field support + else + raise "Unhandled link type of '#{type}' for '#{name}' with link_text '#{link_text}'" + end + end + end + end +end diff --git a/lib/cfg_arch.rb b/lib/cfg_arch.rb index 2d6f6fa8e..9fd2e4191 100644 --- a/lib/cfg_arch.rb +++ b/lib/cfg_arch.rb @@ -1,64 +1,44 @@ # frozen_string_literal: true -# Many classes include DatabaseObject have an "cfg_arch" member which is a ConfiguredArchitecture class. -# It combines knowledge of the RISC-V Architecture with a particular configuration. -# A configuration is an instance of the Config object either located in the /cfg directory -# or created at runtime for things like profiles and certificate models. +# Combines knowledge of the RISC-V Architecture with a particular configuration. +# The architecture is an instance of the Architecture object representing yaml files in the /arch directory. +# A configuration is an instance of the Config object representing yaml files in the /cfg directory. require "forwardable" require "ruby-prof" -require "tilt" -require_relative "config" require_relative "architecture" +require_relative "design" +require_relative "config" -require_relative "idl" -require_relative "idl/passes/find_return_values" -require_relative "idl/passes/gen_adoc" -require_relative "idl/passes/prune" -require_relative "idl/passes/reachable_exceptions" -require_relative "idl/passes/reachable_functions" - -require_relative "template_helpers" - -include TemplateHelpers - -class ConfiguredArchitecture < Architecture +class ConfiguredArchitecture < Design extend Forwardable - # @return [Idl::Compiler] The IDL compiler - attr_reader :idl_compiler - - # @return [Idl::IsaAst] Abstract syntax tree of global scope - attr_reader :global_ast - - # @return [String] Name of this definition. Special names are: - # * '_' - The generic architecture, with no configuration settings. - # * 'rv32' - A generic RV32 architecture, with only one parameter set (XLEN == 32) - # * 'rv64' - A generic RV64 architecture, with only one parameter set (XLEN == 64) - attr_reader :name - + # Calls to these methods on ConfiguredArchitecture are handled by the @config method of the same name. + # Kind of like inheritence but not quite. def_delegators \ :@config, \ - :fully_configured?, :partially_configured?, :unconfigured?, :configured?, \ - :mxlen, :param_values - - # 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? + :fully_configured?, :partially_configured?, :unconfigured?, :configured?, :param_values - ["S", "U", "VS", "VU"].any? { |mode| multi_xlen_in_mode?(mode) } + # @param config_name [#to_s] The configuration name which corresponds to a folder name under cfg_path + # @param arch_dir [String,Pathname] Path to a directory with a fully merged/resolved architecture definition + # @param overlay_path [String] Optional path to a directory that overlays the architecture + # @param cfg_path [String] Optional path to where to find configuration file + def initialize(config_name, arch_dir, overlay_path: nil, cfg_path: "#{$root}/cfgs") + @config = Config.create("#{cfg_path}/#{config_name}/cfg.yaml") + super(config_name, Architecture.new(config_name, arch_dir), @config.mxlen, overlay_path: overlay_path) end + # Returns a string representation of the object, suitable for debugging. + # @return [String] A string representation of the object. + def inspect = "ConfiguredArchitecture##{name}" + + ########################################### + # OVERRIDEN ABSTRACT METHODS # + # # + # These raise an error in the base class. # + ########################################### + # 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: @@ -72,11 +52,11 @@ def multi_xlen? # @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) - return false if mxlen == 32 + return false if @mxlen == 32 case mode when "M" - mxlen.nil? + @mxlen.nil? when "S" return true if unconfigured? @@ -146,123 +126,7 @@ def multi_xlen_in_mode?(mode) end end - # @return [Array] List of possible XLENs in any mode for this config - def possible_xlens = multi_xlen? ? [32, 64] : [mxlen] - - # hash for Hash lookup - def hash = @name_sym.hash - - # @return [Idl::SymbolTable] Symbol table with global scope - # @return [nil] if the architecture is not configured (use symtab_32 or symtab_64) - def symtab - raise NotImplementedError, "Un-configured ConfiguredArchitectures have no symbol table" if @symtab.nil? - - @symtab - end - - def config_type = @config.type - - # Initialize a new configured architecture definition - # - # @param config_name [#to_s] The name of a configuration, which must correspond - # to a folder name under cfg_path - def initialize(config_name, arch_path, overlay_path: nil, cfg_path: "#{$root}/cfgs") - super(arch_path) - - @name = config_name.to_s.freeze - @name_sym = @name.to_sym.freeze - - @obj_cache = {} - - @config = Config.create("#{cfg_path}/#{config_name}/cfg.yaml") - @mxlen = @config.mxlen - @mxlen.freeze - - @idl_compiler = Idl::Compiler.new - - @symtab = 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 - ) - @global_ast.add_global_symbols(@symtab) - @symtab.deep_freeze - @global_ast.freeze_tree(@symtab) - end - - # Returns a string representation of the object, suitable for debugging. - # @return [String] A string representation of the object. - def inspect = "ConfiguredArchitecture##{name}" - - # type check all IDL, including globals, instruction ops, and CSR functions - # - # @param config [Config] Configuration - # @param show_progress [Boolean] whether to show progress bars - # @param io [IO] where to write progress bars - # @return [void] - def type_check(show_progress: true, io: $stdout) - io.puts "Type checking IDL code for #{@config.name}..." - progressbar = - if show_progress - ProgressBar.create(title: "Instructions", total: instructions.size) - end - - instructions.each do |inst| - progressbar.increment if show_progress - if @mxlen == 32 - inst.type_checked_operation_ast(@idl_compiler, @symtab, 32) if inst.rv32? - elsif @mxlen == 64 - inst.type_checked_operation_ast(@idl_compiler, @symtab, 64) if inst.rv64? - inst.type_checked_operation_ast(@idl_compiler, @symtab, 32) if possible_xlens.include?(32) && inst.rv32? - end - end - - progressbar = - if show_progress - ProgressBar.create(title: "CSRs", total: csrs.size) - end - - csrs.each do |csr| - progressbar.increment if show_progress - if csr.has_custom_sw_read? - if (possible_xlens.include?(32) && csr.defined_in_base32?) || (possible_xlens.include?(64) && csr.defined_in_base64?) - csr.type_checked_sw_read_ast(@symtab) - end - end - csr.fields.each do |field| - unless field.type_ast(@symtab).nil? - if ((possible_xlens.include?(32) && csr.defined_in_base32? && field.defined_in_base32?) || - (possible_xlens.include?(64) && csr.defined_in_base64? && field.defined_in_base64?)) - field.type_checked_type_ast(@symtab) - end - end - unless field.reset_value_ast(@symtab).nil? - if ((possible_xlens.include?(32) && csr.defined_in_base32? && field.defined_in_base32?) || - (possible_xlens.include?(64) && csr.defined_in_base64? && field.defined_in_base64?)) - field.type_checked_reset_value_ast(@symtab) if csr.defined_in_base32? && field.defined_in_base32? - end - end - unless field.sw_write_ast(@symtab).nil? - field.type_checked_sw_write_ast(@symtab, 32) if possible_xlens.include?(32) && csr.defined_in_base32? && field.defined_in_base32? - field.type_checked_sw_write_ast(@symtab, 64) if possible_xlens.include?(64) && csr.defined_in_base64? && field.defined_in_base64? - end - end - end - - progressbar = - if show_progress - ProgressBar.create(title: "Functions", total: functions.size) - end - functions.each do |func| - progressbar.increment if show_progress - func.type_check(@symtab) - end - - puts "done" if show_progress - end - - # @return [Array] List of all parameters with one known value in the config + # @return [Array] List of all parameters with one known value in the config def params_with_value return @params_with_value unless @params_with_value.nil? @@ -270,27 +134,25 @@ def params_with_value return @params_with_value if @config.unconfigured? if @config.fully_configured? - transitive_implemented_extensions.each do |ext_version| - ext = extension(ext_version.name) - ext.params.each do |ext_param| - next unless @config.param_values.key?(ext_param.name) - - @params_with_value << ExtensionParameterWithValue.new( - ext_param, - @config.param_values[ext_param.name] + transitive_implemented_ext_vers.each do |ext_version| + ext_version.ext.params.each do |param| + next unless @config.param_values.key?(param.name) + + @params_with_value << ParameterWithValue.new( + param, + @config.param_values[param.name] ) end end elsif @config.partially_configured? - mandatory_extensions.each do |ext_requirement| - ext = extension(ext_requirement.name) - ext.params.each do |ext_param| + mandatory_ext_reqs.each do |ext_req| + ext_req.extension.params.each do |param| # Params listed in the config always only have one value. - next unless @config.param_values.key?(ext_param.name) + next unless @config.param_values.key?(param.name) - @params_with_value << ExtensionParameterWithValue.new( - ext_param, - @config.param_values[ext_param.name] + @params_with_value << ParameterWithValue.new( + param, + @config.param_values[param.name] ) end end @@ -300,101 +162,90 @@ def params_with_value @params_with_value end - # @return [Array] List of all available parameters without one known value in the config + # @return [Array] List of all available parameters without one known value in the config def params_without_value return @params_without_value unless @params_without_value.nil? @params_without_value = [] - extensions.each do |ext| - ext.params.each do |ext_param| + arch.extensions.each do |ext| + ext.params.each do |param| # Params listed in the config always only have one value. - next if @config.param_values.key?(ext_param.name) + next if @config.param_values.key?(param.name) - @params_without_value << ext_param + @params_without_value << param end end @params_without_value end - def implemented_extensions - @implemented_extensions ||= - @config.implemented_extensions.map do |e| - ExtensionVersion.new(e["name"], e["version"], self, fail_if_version_does_not_exist: true) - end - end - - # @return [Array] List of all extensions known to be implemented in this config, including transitive implications - def transitive_implemented_extensions - return @transitive_implemented_extensions unless @transitive_implemented_extensions.nil? + # @return [Array] List of all implemented extension versions. + def implemented_ext_vers + return @implemented_ext_vers unless @implemented_ext_vers.nil? - raise "implemented_extensions is only valid for a fully configured definition" unless @config.fully_configured? - - list = implemented_extensions - list.each do |e| - implications = e.transitive_implications - list.concat(implications) unless implications.empty? + @implemented_ext_vers = @config.implemented_extensions.map do |e| + ExtensionVersion.new(e["name"], e["version"], arch, fail_if_version_does_not_exist: true) end - @transitive_implemented_extensions = list.uniq.sort end # @return [Array] List of all mandatory extension requirements - def mandatory_extensions - @mandatory_extensions ||= - @config.mandatory_extensions.map do |e| - ext = extension(e["name"]) - raise "Cannot find extension #{e['name']} in the architecture definition" if ext.nil? + def mandatory_ext_reqs + return @mandatory_ext_reqs unless @mandatory_ext_reqs.nil? - ExtensionRequirement.new(e["name"], *e["version"], presence: "mandatory", arch: self) - end + @mandatory_ext_reqs = @config.mandatory_extensions.map do |e| + ext = arch.extension(e["name"]) + raise "Cannot find extension #{e['name']} in the architecture definition" if ext.nil? + + ExtensionRequirement.new(e["name"], *e["version"], arch, presence: "mandatory") + end end # @return [Array] List of all extensions that are prohibited. # This includes extensions explicitly prohibited by the config file # and extensions that conflict with a mandatory extension. - def prohibited_extensions - return @prohibited_extensions unless @prohibited_extensions.nil? + def prohibited_ext_reqs + return @prohibited_ext_reqs unless @prohibited_ext_reqs.nil? if @config.partially_configured? - @prohibited_extensions = + @prohibited_ext_reqs = @config.prohibited_extensions.map do |e| - ext = extension(e["name"]) + ext = arch.extension(e["name"]) raise "Cannot find extension #{e['name']} in the architecture definition" if ext.nil? - ExtensionRequirement.new(e["name"], *e["version"], presence: "mandatory", arch: self) + ExtensionRequirement.new(e["name"], *e["version"], arch, presence: "mandatory") end # now add any extensions that are prohibited by a mandatory extension - mandatory_extensions.each do |ext_req| + mandatory_ext_reqs.each do |ext_req| ext_req.extension.conflicts.each do |conflict| - if @prohibited_extensions.none? { |prohibited_ext| prohibited_ext.name == conflict.name } - @prohibited_extensions << conflict + if @prohibited_ext_reqs.none? { |prohibited_ext_req| prohibited_ext_req.name == conflict.name } + @prohibited_ext_reqs << conflict else # pick whichever requirement is more expansive - p = @prohibited_extensions.find { |prohibited_ext| prohibited_ext.name == conflict.name } + p = @prohibited_ext_reqs.find { |prohibited_ext_req| prohibited_ext_req.name == confict.name } if p.version_requirement.subsumes?(conflict.version_requirement) - @prohibited_extensions.delete(p) - @prohibited_extensions << conflict + @prohibited_ext_reqs.delete(p) + @prohibited_ext_reqs << conflict end end end end - @prohibited_extensions + @prohibited_ext_reqs elsif @config.fully_configured? - prohibited_ext_versions = [] - extensions.each do |ext| + prohibited_ext_vers = [] + arch.extensions.each do |ext| ext.versions.each do |ext_ver| - prohibited_ext_versions << ext_ver unless transitive_implemented_extensions.include?(ext_ver) + prohibited_ext_vers << ext_ver unless transitive_implemented_ext_vers.include?(ext_ver) end end - @prohibited_extensions = [] - prohibited_ext_versions.group_by(&:name).each_value do |ext_ver_list| + @prohibited_ext_reqs = [] + prohibited_ext_vers.group_by(&:name).each_value do |ext_ver_list| if ext_ver_list.sort == ext_ver_list[0].ext.versions.sort # excludes every version - @prohibited_extensions << + @prohibited_ext_reqs << ExtensionRequirement.new( ext_ver_list[0].ext.name, ">= #{ext_ver_list.min.version_spec.canonical}", - presence: "prohibited", arch: self + arch, presence: "prohibited" ) elsif ext_ver_list.size == (ext_ver_list[0].ext.versions.size - 1) # excludes all but one version @@ -402,10 +253,10 @@ def prohibited_extensions raise "Expected only a single element" unless allowed_version_list.size == 1 allowed_version = allowed_version_list[0] - @prohibited_extensions << + @prohibited_ext_reqs << ExtensionRequirement.new( - ext_ver_list[0].ext.name, "!= #{allowed_version.version_spec.canonical}", - presence: "prohibited", arch: self + ext_ver_list[0].ext.name, "!= #{allowed_version.version_spec.canonical}", arch, + presence: "prohibited" ) else # need to group @@ -413,43 +264,19 @@ def prohibited_extensions end end else - @prohibited_extensions = [] - end - @prohibited_extensions - end - - # @overload prohibited_ext?(ext) - # Returns true if the ExtensionVersion +ext+ is prohibited - # @param ext [ExtensionVersion] An extension version - # @return [Boolean] - # - # @overload prohibited_ext?(ext) - # Returns true if any version of the extension named +ext+ is prohibited - # @param ext [String] An extension name - # @return [Boolean] - def prohibited_ext?(ext) - if ext.is_a?(ExtensionVersion) - prohibited_extensions.any? { |ext_req| ext_req.satisfied_by?(ext) } - elsif ext.is_a?(String) || ext.is_a?(Symbol) - prohibited_extensions.any? { |ext_req| ext_req.name == ext.to_s } - else - raise ArgumentError, "Argument to prohibited_ext? should be an ExtensionVersion or a String" + @prohibited_ext_reqs = [] end + @prohibited_ext_reqs end # @overload ext?(ext_name) # @param ext_name [#to_s] Extension name (case sensitive) - # @return [Boolean] True if the extension `name` is implemented + # @return [Boolean] True if the extension `name` must be 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 - # @return [Boolean] True if the extension `name` meeting `ext_version_requirements` is implemented - # @example Checking extension presence with a version requirement - # ConfigurationArchitecture.ext?(:S, ">= 1.12") - # @example Checking extension presence with multiple version requirements - # ConfigurationArchitecture.ext?(:S, ">= 1.12", "< 1.15") - # @example Checking extension precsence with a precise version requirement - # ConfigurationArchitecture.ext?(:S, 1.12) + # @return [Boolean] True if the extension `name` meeting `ext_version_requirements` must be implemented def ext?(ext_name, *ext_version_requirements) @ext_cache ||= {} cached_result = @ext_cache[[ext_name, ext_version_requirements]] @@ -457,20 +284,20 @@ def ext?(ext_name, *ext_version_requirements) result = if @config.fully_configured? - transitive_implemented_extensions.any? do |e| + transitive_implemented_ext_vers.any? do |e| if ext_version_requirements.empty? e.name == ext_name.to_s else - requirement = ExtensionRequirement.new(ext_name, *ext_version_requirements, arch: self) + requirement = ExtensionRequirement.new(ext_name, *ext_version_requirements, arch) requirement.satisfied_by?(e) end end elsif @config.partially_configured? - mandatory_extensions.any? do |e| + mandatory_ext_reqs.any? do |e| if ext_version_requirements.empty? e.name == ext_name.to_s else - requirement = ExtensionRequirement.new(ext_name, *ext_version_requirements, arch: self) + requirement = ExtensionRequirement.new(ext_name, *ext_version_requirements, arch) e.satisfying_versions.all? do |ext_ver| requirement.satisfied_by?(ext_ver) end @@ -484,223 +311,45 @@ def ext?(ext_name, *ext_version_requirements) @ext_cache[[ext_name, ext_version_requirements]] = result 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 known to be implemented - def implemented_interrupt_codes - return @implemented_interrupt_codes unless @implemented_interrupt_codes.nil? - - @implemented_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"] } - - 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 - - # @return [Array] List of all functions defined by the architecture - def functions - return @functions unless @functions.nil? - - @functions = @global_ast.functions - end - - # @return [Array] List of all implemented CSRs - def transitive_implemented_csrs - @transitive_implemented_csrs ||= - transitive_implemented_extensions.map(&:implemented_csrs).flatten.uniq.sort - end - - # @return [Array] List of all implemented instructions - def transitive_implemented_instructions - @transitive_implemented_instructions ||= - transitive_implemented_extensions.map(&:implemented_instructions).flatten.uniq.sort - 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" - - transitive_implemented_instructions.each do |inst| - @implemented_functions << - if inst.base.nil? - if multi_xlen? - (inst.reachable_functions(symtab, 32) + - inst.reachable_functions(symtab, 64)) - else - inst.reachable_functions(symtab, mxlen) - end - else - inst.reachable_functions(symtab, inst.base) - end - end - raise "?" unless @implemented_functions.is_a?(Array) - @implemented_functions = @implemented_functions.flatten - @implemented_functions.uniq!(&:name) - - puts " Finding all reachable functions from CSR operations" - - transitive_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 - - @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 + # Given an adoc string, find names of CSR/Instruction/Extension enclosed in `monospace` + # and replace them with links to the relevant object page. + # See backend_helpers.rb for a definition of the proprietary link format. # # @param adoc [String] Asciidoc source # @return [String] Asciidoc source, with link placeholders - def find_replace_links(adoc) + def convert_monospace_to_links(adoc) adoc.gsub(/`([\w.]+)`/) do |match| name = Regexp.last_match(1) csr_name, field_name = name.split(".") - csr = csr(csr_name) + csr = arch.csr(csr_name) if !field_name.nil? && !csr.nil? && csr.field?(field_name) - "%%LINK%csr_field;#{csr_name}.#{field_name};#{csr_name}.#{field_name}%%" + link_to_udb_doc_csr_field(csr_name, field_name) elsif !csr.nil? - "%%LINK%csr;#{csr_name};#{csr_name}%%" - elsif instruction(name) - "%%LINK%inst;#{name};#{name}%%" - elsif extension(name) - "%%LINK%ext;#{name};#{name}%%" + link_to_udb_doc_csr(csr_name) + elsif arch.instruction(name) + link_to_udb_doc_inst(name) + elsif arch.extension(name) + link_to_udb_doc_ext(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? - - @env = Class.new - @env.instance_variable_set(:@cfg, @cfg) - @env.instance_variable_set(:@cfg_arch, self) - @env.instance_variable_set(:@params, @params) - - # add each parameter, either as a method (lowercase) or constant (uppercase) - params_with_value.each do |param| - @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 + ##################################### + # METHODS RESTRICTING PARENT METHOD # + ##################################### - @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") - @cfg_arch.ext?(ext_name.to_s, ext_requirement) - end - - # @return [Array] List of possible XLENs for any implemented mode - def possible_xlens - @cfg_arch.possible_xlens - end - - # 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}%%" - end - - # info on interrupt and exception codes - - # @returns [Hash] architecturally-defined exception codes and their names - def exception_codes - @cfg_arch.exception_codes - end - - # returns [Hash] architecturally-defined interrupt codes and their names - def interrupt_codes - @cfg_arch.interrupt_codes - end - - # @returns [Hash] architecturally-defined exception codes and their names - def implemented_exception_codes - @cfg_arch.implemented_exception_codes - end - - # returns [Hash] architecturally-defined interrupt codes and their names - def implemented_interrupt_codes - @cfg_arch.implemented_interrupt_codes - end - end - - @env + # @return [Array] List of all extensions known to be implemented in this config, including transitive implications + def transitive_implemented_ext_vers + raise "transitive_implemented_ext_vers is only valid for a fully configured definition" unless @config.fully_configured? + super 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 - end + # Override base class to reject a nil value. + # @return [Idl::SymbolTable] Symbol table with global scope + def symtab + raise NotImplementedError, "Un-configured ConfiguredArchitectures have no symbol table" if @symtab.nil? + super end end diff --git a/lib/config.rb b/lib/config.rb index 5efe7b684..e52b84e54 100644 --- a/lib/config.rb +++ b/lib/config.rb @@ -3,7 +3,7 @@ require "pathname" # This class represents a configuration file (e.g., cfgs/*/cfg.yaml), independent of the Architecture. -# Can either be in the /cfg directory or created at runtime in memory by the certificate tasks.rake file. +# Located in the /cfg directory. class Config # @return [Hash] A hash mapping parameter name to value for any parameter that has # been configured with a value. May be empty. @@ -142,10 +142,6 @@ def prohibited_extensions end end end - - # def prohibited_ext?(ext_name, cfg_arch) = prohibited_extensions(cfg_arch).any? { |e| e.name == ext_name.to_s } - - # def ext?(ext_name, cfg_arch) = mandatory_extensions(cfg_arch).any? { |e| e.name == ext_name.to_s } end ################################################################################################################ @@ -182,7 +178,4 @@ def implemented_extensions def mandatory_extensions = raise "mandatory_extensions is only available for a PartialConfig" def prohibited_extensions = raise "prohibited_extensions is only available for a PartialConfig" - - # def prohibited_ext?(ext_name, cfg_arch) = !ext?(ext_name, cfg_arch) - # def ext?(ext_name, cfg_arch) = implemented_extensions(cfg_arch).any? { |e| e.name == ext_name.to_s } end diff --git a/lib/design.rb b/lib/design.rb new file mode 100644 index 000000000..afe481743 --- /dev/null +++ b/lib/design.rb @@ -0,0 +1,393 @@ +# frozen_string_literal: true + +# The Design class assists backends when exporting information from the database. +# It contains common code such as IDL and ERB support used by multiple backends. +# +# A Design provides support when exporting any of the following to ASCIIDOC/HTML/PDF: +# - Entire RISC-V ISA manual +# - Individual Extension +# - Profile Release +# - CRD (Certificate Requirements Document) +# - CTP (Certificate Test Plan) +# +# The Design class contains an Architecture object but isn't inherited from it. +# This was done so code that only needs an Architecture object can make this clear +# by using the Architecture object instead of the Design object (i.e., to support encapsulation). +# +# This Design class is an abstract base class for designs using either a config (under /cfg) or a +# portfolio (profile release or certificate). The abstract methods exist in the IDesign base class +# and call raise() if not overriden by a child class. + +require "ruby-prof" +require "tilt" + +require_relative "idesign" +require_relative "architecture" + +require_relative "idl" +require_relative "idl/passes/find_return_values" +require_relative "idl/passes/gen_adoc" +require_relative "idl/passes/prune" +require_relative "idl/passes/reachable_exceptions" +require_relative "idl/passes/reachable_functions" + +require_relative "backend_helpers" + +include TemplateHelpers + +class Design < IDesign + # @return [Architecture] The RISC-V architecture + attr_reader :arch + + # @return [Integer] 32, 64, or nil for dynamic + attr_reader :mxlen + + # @return [Idl::Compiler] The IDL compiler + attr_reader :idl_compiler + + # @return [Idl::IsaAst] Abstract syntax tree of global scope + attr_reader :global_ast + + # @return [Idl::SymbolTable] Symbol table with global scope + # Don't use attr_reader so this can be clearly overridden by child class. + def symtab = @symtab + + # hash for Hash lookup + def hash = @name_sym.hash + + # @param name [#to_s] The design name + # @param arch [Architecture] The entire architecture + # @param mxlen [Integer] 32, 64, or nil for dynamic + # @param overlay_path [String] Optional path to a directory that overlays the architecture + def initialize(name, arch, mxlen, overlay_path: nil) + super(name) + + raise ArgumentError, "arch must be an Architecture but is a #{arch.class}" unless arch.is_a?(Architecture) + @arch = arch + + @mxlen = mxlen + @mxlen.freeze + + @idl_compiler = Idl::Compiler.new + @symtab = 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) + @global_ast.add_global_symbols(@symtab) + @symtab.deep_freeze + @global_ast.freeze_tree(@symtab) + end + + # Returns a string representation of the object, suitable for debugging. + # @return [String] A string representation of the object. + def inspect = "Design##{name}" + + # 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 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 + + # @return [Array] List of possible XLENs in any mode for this design + def possible_xlens = multi_xlen? ? [32, 64] : [mxlen] + + # type check all IDL, including globals, instruction ops, and CSR functions + # + # @param show_progress [Boolean] whether to show progress bars + # @param io [IO] where to write progress bars + # @return [void] + def type_check(show_progress: true, io: $stdout) + io.puts "Type checking IDL code for #{@name}..." + progressbar = + if show_progress + ProgressBar.create(title: "Instructions", total: arch.instructions.size) + end + + arch.instructions.each do |inst| + progressbar.increment if show_progress + if @mxlen == 32 + inst.type_checked_operation_ast(@idl_compiler, @symtab, 32) if inst.rv32? + elsif @mxlen == 64 + inst.type_checked_operation_ast(@idl_compiler, @symtab, 64) if inst.rv64? + inst.type_checked_operation_ast(@idl_compiler, @symtab, 32) if possible_xlens.include?(32) && inst.rv32? + end + end + + progressbar = + if show_progress + ProgressBar.create(title: "CSRs", total: arch.csrs.size) + end + + arch.csrs.each do |csr| + progressbar.increment if show_progress + if csr.has_custom_sw_read? + if (possible_xlens.include?(32) && csr.defined_in_base32?) || (possible_xlens.include?(64) && csr.defined_in_base64?) + csr.type_checked_sw_read_ast(@symtab) + end + end + csr.fields.each do |field| + unless field.type_ast(@symtab).nil? + if ((possible_xlens.include?(32) && csr.defined_in_base32? && field.defined_in_base32?) || + (possible_xlens.include?(64) && csr.defined_in_base64? && field.defined_in_base64?)) + field.type_checked_type_ast(@symtab) + end + end + unless field.reset_value_ast(@symtab).nil? + if ((possible_xlens.include?(32) && csr.defined_in_base32? && field.defined_in_base32?) || + (possible_xlens.include?(64) && csr.defined_in_base64? && field.defined_in_base64?)) + field.type_checked_reset_value_ast(@symtab) if csr.defined_in_base32? && field.defined_in_base32? + end + end + unless field.sw_write_ast(@symtab).nil? + field.type_checked_sw_write_ast(@symtab, 32) if possible_xlens.include?(32) && csr.defined_in_base32? && field.defined_in_base32? + field.type_checked_sw_write_ast(@symtab, 64) if possible_xlens.include?(64) && csr.defined_in_base64? && field.defined_in_base64? + end + end + end + + progressbar = + if show_progress + ProgressBar.create(title: "Functions", total: functions.size) + end + functions.each do |func| + progressbar.increment if show_progress + func.type_check(@symtab) + end + + puts "done" if show_progress + end + + # @return [Array] List of all extension versions known to be implemented in this design, + # including transitive implications. + def transitive_implemented_ext_vers + return @transitive_implemented_ext_vers unless @transitive_implemented_ext_vers.nil? + + list = implemented_ext_vers + list.each do |ext_ver| + implications = ext_ver.transitive_implications + list.concat(implications) unless implications.empty? + end + @transitive_implemented_ext_vers = list.uniq.sort + end + + # @overload prohibited_ext?(ext) + # Returns true if the ExtensionVersion +ext+ is prohibited + # @param ext [ExtensionVersion] An extension version + # @return [Boolean] + # + # @overload prohibited_ext?(ext) + # Returns true if any version of the extension named +ext+ is prohibited + # @param ext [String] An extension name + # @return [Boolean] + def prohibited_ext?(ext) + if ext.is_a?(ExtensionVersion) + prohibited_ext_reqs.any? { |ext_req| ext_req.satisfied_by?(ext) } + elsif ext.is_a?(String) || ext.is_a?(Symbol) + prohibited_ext_reqs.any? { |ext_req| ext_req.name == ext.to_s } + else + raise ArgumentError, "Argument to prohibited_ext? should be an ExtensionVersion or a String" + 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_ext_vers.reduce([]) do |list, ext_version| + ecodes = ext_version.ext["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"], arch) + end + list + end + end + + # @return [Array] All interrupt codes known to be implemented + def implemented_interrupt_codes + return @implemented_interrupt_codes unless @implemented_interrupt_codes.nil? + + @implemented_interupt_codes = + implemented_ext_vers.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"] } + + 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"], arch) + end + list + end + end + + # @return [Array] Sorted list of all IDL functions defined by the architecture + def functions + return @functions unless @functions.nil? + + @functions = @global_ast.functions.sort + end + + # @return [Array] Sorted list of all implemented CSRs + def transitive_implemented_csrs + @transitive_implemented_csrs ||= + transitive_implemented_ext_vers.map(&:implemented_csrs).flatten.uniq.sort + end + + # @return [Array] Sorted list of all implemented instructions + def transitive_implemented_instructions + @transitive_implemented_instructions ||= + transitive_implemented_ext_vers.map(&:implemented_instructions).flatten.uniq.sort + end + + # @return [Array] Sorted list of all reachable IDL functions for the design + def implemented_functions + return @implemented_functions unless @implemented_functions.nil? + + @implemented_functions = [] + + puts " Finding all reachable functions from instruction operations" + + transitive_implemented_instructions.each do |inst| + @implemented_functions << + if inst.base.nil? + if multi_xlen? + (inst.reachable_functions(symtab, 32) + + inst.reachable_functions(symtab, 64)) + else + inst.reachable_functions(symtab, mxlen) + end + else + inst.reachable_functions(symtab, inst.base) + end + end + raise "?" unless @implemented_functions.is_a?(Array) + @implemented_functions = @implemented_functions.flatten.uniq(&:name) + + puts " Finding all reachable functions from CSR operations" + + transitive_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 + + @implemented_functions.sort! + end + + # Returns an environment hash suitable for the render_erb() function in ERB templates. + # + # @return [Hash] An environment hash suitable for use with ERb templates. + def render_erb_env + return @env unless @env.nil? + + @env = Class.new + @env.instance_variable_set(:@design, self) + @env.instance_variable_set(:@params, @param_values) + @env.instance_variable_set(:@arch, @arch) + + # add each parameter, either as a method (lowercase) or constant (uppercase) + params_with_value.each do |param| + @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 + # + # @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 design + def ext?(ext_name, ext_requirement = ">= 0") + @design.ext?(ext_name.to_s, ext_requirement) + end + + # @return [Array] List of possible XLENs for any implemented mode + def possible_xlens + @design.possible_xlens + end + + # 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 + # + # @param type [Symbol] Type (:section, :csr, :inst, :ext) + # @param name [#to_s] Name of the object + def link_to_udb(type, name) + "%%UDB_DOC_LINK%#{type};#{name}%%" + end + + # @returns [Hash] architecturally-defined exception codes and their names + def exception_codes + @arch.exception_codes + end + + # returns [Hash] architecturally-defined interrupt codes and their names + def interrupt_codes + @arch.interrupt_codes + end + + # @returns [Hash] architecturally-defined exception codes and their names + def implemented_exception_codes + @design.implemented_exception_codes + end + + # returns [Hash] architecturally-defined interrupt codes and their names + def implemented_interrupt_codes + @design.implemented_interrupt_codes + end + end + + @env + end + private :render_erb_env + + # Passes _erb_template_ through ERB within the content of the render_erb_env + # + # @param erb_template [String] ERB template source string + # @param what [String] ??? + # @return [String] The rendered text + def render_erb(erb_template, what = "") + t = Tempfile.new("template") + t.write erb_template + t.flush + begin + template = Tilt["erb"].new(t.path, trim: "-") + template.render(render_erb_env) + rescue + warn "While rendering ERB template #{erb_template}: #{what}" + raise + ensure + t.close + t.unlink + end + end +end diff --git a/lib/idesign.rb b/lib/idesign.rb new file mode 100644 index 000000000..d1fe0c0a0 --- /dev/null +++ b/lib/idesign.rb @@ -0,0 +1,113 @@ +# frozen_string_literal: true + +# The IDesign class (Interface Design) contains the abstract methods for the Design class. +# Putting these methods into IDesign rather than Design allows unit-level tests to create +# a MockDesign class that only has to implement the require abstract interfaces without all +# the other baggage that a Design class adds (e.g., having an Architecture object). + +class IDesign + # @return [String] Name of design + attr_reader :name + + # @param name [#to_s] The design name + def initialize(name) + @name = name.to_s.freeze + @name_sym = @name.to_sym.freeze + end + + # @return [Boolean] True if not unconfigured (so either fully_configured or partially_configured). + def configured? = !unconfigured? + + #################### + # ABSTRACT METHODS # + #################### + + # @return [Integer] 32, 64, or nil (if dynamic or unconfigured) + def mxlen + raise "Abstract Method: Must be provided in child class" + end + + # Returns whether or not it may be possible to switch XLEN in +mode+ given this definition. + # @param mode [String] mode to check. One of "M", "S", "U", "VS", "VU" + # @return [Boolean] true if 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) + raise "Abstract Method: Must be provided in child class" + end + + # @return [Hash] Fully-constrained parameter values (those with just one possible value for this design) + def param_values + raise "Abstract Method: Must be provided in child class" + end + + # @return [Array] List of all parameters fully-constrained to one specific value + def params_with_value + raise "Abstract Method: Must be provided in child class" + end + + # @return [Array] List of all available parameters not yet full-constrained to one specific value + def params_without_value + raise "Abstract Method: Must be provided in child class" + end + + # @return [Array] List of all implemented extension versions. + def implemented_ext_vers + raise "Abstract Method: Must be provided in child class" + end + + # @return [Array] List of all mandatory extension requirements + def mandatory_ext_reqs + raise "Abstract Method: Must be provided in child class" + end + + # @return [Array] List of all extensions that are prohibited. + # This includes extensions explicitly prohibited by the design + # and extensions that conflict with a mandatory extension. + def prohibited_ext_reqs + raise "Abstract Method: Must be provided in child class" + end + + # @return [Boolean] True if all parameters are fully-constrained in the design + def fully_configured? + raise "Abstract Method: Must be provided in child class" + end + + # @return [Boolean] True if some parameters aren't fully-constrained yet in the design + def partially_configured? + raise "Abstract Method: Must be provided in child class" + end + + # @return [Boolean] True if all parameters aren't constrained at all in the design + def unconfigured? + raise "Abstract Method: Must be provided in child class" + end + + # @overload ext?(ext_name) + # @param ext_name [#to_s] Extension name (case sensitive) + # @return [Boolean] True if the extension `name` must be 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 + # @return [Boolean] True if the extension `name` meeting `ext_version_requirements` must be implemented + # + # @example Checking extension presence with a version requirement + # Design.ext?(:S, ">= 1.12") + # @example Checking extension presence with multiple version requirements + # Design.ext?(:S, ">= 1.12", "< 1.15") + # @example Checking extension presence with a precise version requirement + # Design.ext?(:S, 1.12) + def ext?(ext_name, *ext_version_requirements) + raise "Abstract Method: Must be provided in child class" + end + + # Given an adoc string, find names of CSR/Instruction/Extension enclosed in `monospace` + # and replace them with links to the relevant object page. + # See backend_helpers.rb for a definition of the proprietary link format. + # + # @param adoc [String] Asciidoc source + # @return [String] Asciidoc source, with link placeholders + def convert_monospace_to_links(adoc) + raise "Abstract Method: Must be provided in child class" + end +end diff --git a/lib/idl/ast.rb b/lib/idl/ast.rb index d77aaa27c..cf62a22c3 100644 --- a/lib/idl/ast.rb +++ b/lib/idl/ast.rb @@ -564,7 +564,7 @@ def type_check(symtab) type_error "no symbol named '#{name}' on line #{lineno}" if symtab.get(name).nil? end - # @!macro type_no_cfg_arch + # @!macro type_no_design def type(symtab) return @type unless @type.nil? @@ -588,11 +588,11 @@ def const? = @const def value(symtab) # can't do this.... a const might be in a template function, with different values at call time # if @const - # # consts never change, so we can look them up by cfg_arch - # var = @vars[symtab.cfg_arch] + # # consts never change, so we can look them up by design + # var = @vars[symtab.design] # if var.nil? # var = symtab.get(name) - # @vars[symtab.cfg_arch] = var + # @vars[symtab.design] = var # end # type_error "Variable '#{name}' was not found" if var.nil? # value_error "Value of '#{name}' not known" if var.value.nil? @@ -731,13 +731,13 @@ def globals = definitions.select { |d| d.is_a?(GlobalWithInitializationAst) || d # @return {Array] List of all enum definitions def enums = definitions.select { |e| e.is_a?(EnumDefinitionAst) || e.is_a?(BuiltinEnumDefinitionAst) } - # @return {Array] List of all bitfield definitions + # @return {Array] List of all bitfield definitions def bitfields = definitions.select { |e| e.is_a?(BitfieldDefinitionAst) } - # @return [Array] List of all struct definitions + # @return [Array] List of all struct definitions def structs = definitions.select { |e| e.is_a?(StructDefinitionAst) } - # @return {Array] List of all function definitions + # @return {Array] List of all function definitions def functions = definitions.select { |e| e.is_a?(FunctionDefAst) } # Add all the global symbols to symtab @@ -793,7 +793,7 @@ def type_check(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.cfg_arch.fully_configured? && (expression_type.width == :unknown) + if symtab.design.fully_configured? && (expression_type.width == :unknown) type_error "#{expression.text_value} must have a known value at compile time" end end @@ -1063,7 +1063,7 @@ def type(symtab) end # @!macro value_no_args - def value(_symtab, _cfg_arch) = raise InternalError, "Enum definitions have no value" + def value(_symtab, _design) = raise InternalError, "Enum defintions have no value" # @return [String] enum name def name = @user_type.text_value @@ -1119,11 +1119,11 @@ def type_check(_symtab) def element_names(symtab) case name when "ExtensionName" - symtab.cfg_arch.extensions.map(&:name) + symtab.design.arch.extensions.map(&:name) when "ExceptionCode" - symtab.cfg_arch.exception_codes.map(&:var) + symtab.design.arch.exception_codes.map(&:var) when "InterruptCode" - symtab.cfg_arch.interrupt_codes.map(&:var) + symtab.design.arch.interrupt_codes.map(&:var) else type_error "Unknown builtin enum type '#{name}'" end @@ -1132,17 +1132,17 @@ def element_names(symtab) def element_values(symtab) case name when "ExtensionName" - (0...symtab.cfg_arch.extensions.size).to_a + (0...symtab.design.arch.extensions.size).to_a when "ExceptionCode" - symtab.cfg_arch.exception_codes.map(&:num) + symtab.design.arch.exception_codes.map(&:num) when "InterruptCode" - symtab.cfg_arch.interrupt_codes.map(&:num) + symtab.design.arch.interrupt_codes.map(&:num) else type_error "Unknown builtin enum type '#{name}'" end end - # @!macro type_no_cfg_arch + # @!macro type_no_design def type(symtab) return @type unless @type.nil? @@ -1328,7 +1328,7 @@ def type(symtab) def name = @name.text_value # @!macro value_no_args - def value(_symtab, _cfg_arch) = raise AstNode::InternalError, "Bitfield definitions have no value" + def value(_symtab, _design) = raise AstNode::InternalError, "Bitfield defintions have no value" # @!macro to_idl def to_idl @@ -1689,10 +1689,10 @@ def type_check(symtab) end def var(symtab) - variable = @vars[symtab.cfg_arch] + variable = @vars[symtab.design] if variable.nil? variable = symtab.get(lhs.text_value) - @vars[symtab.cfg_arch] = variable + @vars[symtab.design] = variable end variable end @@ -2023,15 +2023,15 @@ def initialize(input, interval, csr_field, write_value) def type(symtab) if field(symtab).defined_in_all_bases? - if symtab.cfg_arch.mxlen == 64 && symtab.cfg_arch.multi_xlen? - Type.new(:bits, width: [field(symtab).location(symtab.cfg_arch, 32).size, field(symtab).location(symtab.cfg_arch, 64).size].max) + if symtab.design.mxlen == 64 && symtab.design.multi_xlen? + Type.new(:bits, width: [field(symtab).location(symtab.design, 32).size, field(symtab).location(symtab.design, 64).size].max) else - Type.new(:bits, width: field(symtab).location(symtab.cfg_arch, symtab.cfg_arch.mxlen).size) + Type.new(:bits, width: field(symtab).location(symtab.design, symtab.design.mxlen).size) end elsif field(symtab).base64_only? - Type.new(:bits, width: field(symtab).location(symtab.cfg_arch, 64).size) + Type.new(:bits, width: field(symtab).location(symtab.design, 64).size) elsif field(symtab).base32_only? - Type.new(:bits, width: field(symtab).location(symtab.cfg_arch, 32).size) + Type.new(:bits, width: field(symtab).location(symtab.design, 32).size) else internal_error "Unexpected base for field" end @@ -2048,7 +2048,7 @@ def type_check(symtab) type_error "Cannot write to read-only CSR field" end end - # ok, we don't know the type because the cfg_arch isn't configured + # ok, we don't know the type because the design isn't configured write_value.type_check(symtab) type_error "Incompatible type in assignment" unless write_value.type(symtab).convertable_to?(type(symtab)) @@ -2275,7 +2275,7 @@ def decl_type(symtab) dtype = Type.new(:array, width: ary_size.value(symtab), sub_type: dtype, qualifiers:) end value_else(value_result) do - type_error "Array size must be known at compile time" if symtab.cfg_arch.fully_configured? + type_error "Array size must be known at compile time" if symtab.design.fully_configured? dtype = Type.new(:array, width: :unknown, sub_type: dtype, qualifiers:) end end @@ -2300,8 +2300,8 @@ def type_check(symtab, add_sym = true) ary_size.value(symtab) end value_else(value_result) do - # if this is a fully configured ConfiguredArchitecture, this is an error because all constants are supposed to be known - if symtab.cfg_arch.fully_configured? + # if this is a fully configured Design, this is an error because all constants are supposed to be known + if symtab.design.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 @@ -2591,10 +2591,10 @@ def type(symtab) when :enum_ref Type.new(:bits, width: etype.enum_class.width) when :csr - if etype.csr.dynamic_length?(symtab.cfg_arch) + if etype.csr.dynamic_length?(symtab.design) Type.new(:bits, width: :unknown) else - Type.new(:bits, width: etype.csr.length(symtab.cfg_arch)) + Type.new(:bits, width: etype.csr.length(symtab.design)) end end end @@ -3449,7 +3449,7 @@ def initialize(input, interval, class_name, member_name) def freeze_tree(global_symtab) return if frozen? - enum_def_ast = global_symtab.cfg_arch.global_ast.enums.find { |e| e.name == @enum_class_name } + enum_def_ast = global_symtab.design.global_ast.enums.find { |e| e.name == @enum_class_name } @enum_def_type = if enum_def_ast.is_a?(BuiltinEnumDefinitionAst) @@ -3471,7 +3471,7 @@ def type_check(symtab) type_error "#{@enum_class_name} has no member '#{@member_name}'" if enum_def_type.value(@member_name).nil? end - # @!macro type_no_cfg_arch + # @!macro type_no_design def type(symtab) internal_error "Not frozen?" unless frozen? type_error "No enum named #{@enum_class_name}" if @enum_def_type.nil? @@ -3479,7 +3479,7 @@ def type(symtab) @enum_def_type.ref_type end - # @!macro value_no_cfg_arch + # @!macro value_no_design def value(symtab) internal_error "Must call type_check first" if @enum_def_type.nil? @@ -3902,7 +3902,7 @@ def type(_symtab) end # @!macro value_no_args - def value(_symtab, _cfg_arch) = internal_error "Why are you calling value for an lval?" + def value(_symtab, _design) = internal_error "Why are you calling value for an lval?" def to_idl = "-" end @@ -4015,7 +4015,7 @@ def expected_return_type(symtab) symtab.get("__expected_return_type") else # need to find the type to get the right symbol table - func_type = @func_type_cache[symtab.cfg_arch] + func_type = @func_type_cache[symtab.design] return func_type.return_type(EMPTY_ARRAY, self) unless func_type.nil? func_type = symtab.get_global(func_def.name) @@ -4034,7 +4034,7 @@ def expected_return_type(symtab) end func_type.return_type(template_values.sort { |a, b| a.template_index <=> b.template_index }.map(&:value), self) else - @func_type_cache[symtab.cfg_arch]= func_type + @func_type_cache[symtab.design]= func_type func_type.return_type(EMPTY_ARRAY, self) end end @@ -4208,7 +4208,7 @@ def type_check(symtab) end end value_else(value_result) do - type_error "Bit width must be known at compile time" if symtab.cfg_arch.fully_configured? + type_error "Bit width must be known at compile time" if symtab.design.fully_configured? end end unless ["Bits", "String", "XReg", "Boolean", "U32", "U64"].include?(@type_name) @@ -4583,7 +4583,7 @@ def arg_nodes end def func_type(symtab) - func_def_type = @func_def_type_cache[symtab.cfg_arch] + func_def_type = @func_def_type_cache[symtab.design] return func_def_type unless func_def_type.nil? func_def_type = symtab.get(@name) @@ -4593,14 +4593,14 @@ def func_type(symtab) type_error "#{@name} is not a function (it's a #{func_def_type.class.name})" end - @func_def_type_cache[symtab.cfg_arch] = func_def_type + @func_def_type_cache[symtab.design] = func_def_type end # @!macro type_check def type_check(symtab) level = symtab.levels - unknown_ok = symtab.cfg_arch.partially_configured? + unknown_ok = symtab.design.partially_configured? tvals = template_values(symtab, unknown_ok:) func_def_type = func_type(symtab) @@ -4652,7 +4652,7 @@ def type_check(symtab) def type(symtab) return ConstBoolType if name == "implemented?" - func_type(symtab).return_type(template_values(symtab, unknown_ok: symtab.cfg_arch.partially_configured?), self) + func_type(symtab).return_type(template_values(symtab, unknown_ok: symtab.design.partially_configured?), self) end # @!macro value @@ -4669,9 +4669,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.cfg_arch.ext?(arg_nodes[0].member_name) if symtab.cfg_arch.fully_configured? + return symtab.design.ext?(arg_nodes[0].member_name) if symtab.design.fully_configured? - if symtab.cfg_arch.ext?(arg_nodes[0].member_name) + if symtab.design.ext?(arg_nodes[0].member_name) # we can know if it is implemented, but not if it's not implemented for a partially configured return true end @@ -4732,12 +4732,12 @@ def type_check(symtab) type_error "#{text_value} is not a type" unless type.is_a?(Type) end - # @!macro type_no_cfg_arch + # @!macro type_no_design def type(symtab) - typ = @type_cache[symtab.cfg_arch] + typ = @type_cache[symtab.design] return typ unless typ.nil? - @type_cache[symtab.cfg_arch] = symtab.get(text_value) + @type_cache[symtab.design] = symtab.get(text_value) end # @!macro to_idl @@ -4897,6 +4897,10 @@ def initialize(input, interval, name, targs, return_types, arguments, desc, body @reachable_functions_cache ||= {} end + def <=>(other) + name <=> other.name + end + attr_reader :reachable_functions_cache # @!macro freeze_tree @@ -4964,7 +4968,7 @@ def arguments_list_str # return the return type, which may be a tuple of multiple types def return_type(symtab) - cached = @cached_return_type[symtab.cfg_arch] + cached = @cached_return_type[symtab.design] return cached unless cached.nil? unless symtab.levels == 2 @@ -4972,12 +4976,12 @@ def return_type(symtab) end if @return_type_nodes.empty? - @cached_return_type[symtab.cfg_arch] = VoidType + @cached_return_type[symtab.design] = VoidType return VoidType end unless templated? - # with no templates, the return type does not change for a given cfg_arch + # with no templates, the return type does not change for a given design rtype = if @return_type_nodes.size == 1 rtype = @return_type_nodes[0].type(symtab) @@ -4995,7 +4999,7 @@ def return_type(symtab) raise "??????" if rtype.nil? - return @cached_return_type[symtab.cfg_arch] = rtype + return @cached_return_type[symtab.design] = rtype end if templated? @@ -5742,7 +5746,7 @@ def freeze_tree(symtab) @value = nil end @type = calc_type(symtab) - @cfg_arch = symtab.cfg_arch # remember cfg_arch, used in gen_adoc pass + @design = symtab.design # remember design, used in gen_adoc pass freeze end @@ -5756,17 +5760,17 @@ def type_check(symtab) type_error "No CSR named #{csr_name}" if csr_def(symtab).nil? end type_error "CSR[#{csr_name(symtab)}] has no field named #{@field_name}" if field_def(symtab).nil? - type_error "CSR[#{csr_name(symtab)}].#{@field_name} is not defined in RV32" if symtab.cfg_arch.mxlen == 32 && !field_def(symtab).defined_in_base32? - type_error "CSR[#{csr_name(symtab)}].#{@field_name} is not defined in RV64" if symtab.cfg_arch.mxlen == 64 && !field_def(symtab).defined_in_base64? + type_error "CSR[#{csr_name(symtab)}].#{@field_name} is not defined in RV32" if symtab.design.mxlen == 32 && !field_def(symtab).defined_in_base32? + type_error "CSR[#{csr_name(symtab)}].#{@field_name} is not defined in RV64" if symtab.design.mxlen == 64 && !field_def(symtab).defined_in_base64? end def csr_def(symtab) - cfg_arch = symtab.cfg_arch + design = symtab.design if @idx.is_a?(IntLiteralAst) - cfg_arch.csrs.find { |c| c.address == @idx.value(symtab) } + design.arch.csrs.find { |c| c.address == @idx.value(symtab) } else - cfg_arch.csr(@idx) + design.arch.csr(@idx) end end @@ -5806,14 +5810,14 @@ def calc_type(symtab) end end if fd.defined_in_all_bases? - Type.new(:bits, width: symtab.cfg_arch.possible_xlens.map{ |xlen| fd.width(symtab.cfg_arch, xlen) }.max) + Type.new(:bits, width: symtab.design.possible_xlens.map{ |xlen| fd.width(symtab.design, xlen) }.max) elsif fd.base64_only? - if symtab.cfg_arch.possible_xlens.include?(64) - Type.new(:bits, width: fd.width(symtab.cfg_arch, 64)) + if symtab.design.possible_xlens.include?(64) + Type.new(:bits, width: fd.width(symtab.design, 64)) end elsif fd.base32_only? - if symtab.cfg_arch.possible_xlens.include?(32) - Type.new(:bits, width: fd.width(symtab.cfg_arch, 32)) + if symtab.design.possible_xlens.include?(32) + Type.new(:bits, width: fd.width(symtab.design, 32)) end else internal_error "unexpected field base" @@ -5837,7 +5841,7 @@ def calc_value(symtab) value_error "'#{csr_name(symtab)}.#{field_name(symtab)}' is not RO" end - field_def(symtab).reset_value(symtab.cfg_arch) + field_def(symtab).reset_value(symtab.design) end end @@ -5877,14 +5881,14 @@ def initialize(input, interval, idx) def freeze_tree(symtab) return if frozen? - @cfg_arch = symtab.cfg_arch # remember cfg_arch, used by gen_adoc pass + @design = symtab.design # remember design, used by gen_adoc pass @idx.freeze_tree(symtab) freeze end # @!macro type def type(symtab) - cfg_arch = symtab.cfg_arch + design = symtab.design cd = csr_def(symtab) if cd.nil? @@ -5896,16 +5900,16 @@ def type(symtab) Bits64Type end else - CsrType.new(cd, cfg_arch) + CsrType.new(cd, design) end end # @!macro type_check def type_check(symtab) - cfg_arch = symtab.cfg_arch + design = symtab.design idx_text = @idx.is_a?(String) ? @idx : @idx.text_value - if !cfg_arch.csr(idx_text).nil? + if !design.arch.csr(idx_text).nil? # this is a known csr name # nothing else to check @@ -5916,7 +5920,7 @@ def type_check(symtab) value_result = value_try do idx_value = @idx.value(symtab) - csr_index = cfg_arch.csrs.index { |csr| csr.address == idx_value } + csr_index = design.arch.csrs.index { |csr| csr.address == idx_value } type_error "No csr number '#{idx_value}' was found" if csr_index.nil? :ok end @@ -5925,9 +5929,9 @@ def type_check(symtab) end def csr_def(symtab) - cfg_arch = symtab.cfg_arch + design = symtab.design idx_text = @idx.is_a?(String) ? @idx : @idx.text_value - csr = cfg_arch.csr(idx_text) + csr = design.arch.csr(idx_text) if !csr.nil? # this is a known csr name csr @@ -5935,7 +5939,7 @@ def csr_def(symtab) # this is an expression value_result = value_try do idx_value = @idx.value(symtab) - return cfg_arch.csrs.find { |csr| csr.address == idx_value } + return design.arch.csrs.find { |csr| csr.address == idx_value } end # || we don't know at compile time which CSR this is... nil @@ -5956,10 +5960,10 @@ def csr_name(symtab) def value(symtab) cd = csr_def(symtab) value_error "CSR number not knowable" if cd.nil? - if symtab.cfg_arch.fully_configured? - value_error "CSR is not implemented" unless symtab.cfg_arch.transitive_implemented_csrs.any? { |icsr| icsr.name == cd.name } + if symtab.design.fully_configured? + value_error "CSR is not implemented" unless symtab.design.transitive_implemented_csrs.any? { |icsr| icsr.name == cd.name } else - value_error "CSR is not defined" unless symtab.cfg_arch.csrs.any? { |icsr| icsr.name == cd.name } + value_error "CSR is not defined" unless symtab.design.arch.csrs.any? { |icsr| icsr.name == cd.name } end cd.fields.each { |f| value_error "#{csr_name(symtab)}.#{f.name} not RO" unless f.type(symtab) == "RO" } @@ -5987,7 +5991,7 @@ def initialize(input, interval, csr, expression) end def type_check(symtab) - cfg_arch = symtab.cfg_arch + design = symtab.design csr.type_check(symtab) expression.type_check(symtab) @@ -6055,12 +6059,12 @@ def type_check(symtab) end def type(symtab) - cfg_arch = symtab.cfg_arch + design = symtab.design case function_name when "sw_read" if csr_known?(symtab) - Type.new(:bits, width: cfg_arch.csr(csr.csr_name(symtab)).length(cfg_arch)) + Type.new(:bits, width: design.arch.csr(csr.csr_name(symtab)).length(design)) else Type.new(:bits, width: symtab.mxlen.nil? ? :unknown : symtab.mxlen) end @@ -6122,10 +6126,10 @@ def initialize(input, interval, idx) def type_check(symtab) if idx.is_a?(IntLiteralAst) # make sure this value is a defined CSR - index = symtab.cfg_arch.csrs.index { |csr| csr.address == idx.value(symtab) } + index = symtab.design.arch.csrs.index { |csr| csr.address == idx.value(symtab) } type_error "No csr number '#{idx.value(symtab)}' was found" if index.nil? else - csr = symtab.cfg_arch.csr(idx.text_value) + csr = symtab.design.arch.csr(idx.text_value) type_error "No csr named '#{idx.text_value}' was found" if csr.nil? end end @@ -6133,15 +6137,15 @@ def type_check(symtab) def csr_def(symtab) if idx.is_a?(IntLiteralAst) # make sure this value is a defined CSR - symtab.cfg_arch.csrs.find { |csr| csr.address == idx.text_value.to_i } + symtab.design.arch.csrs.find { |csr| csr.address == idx.text_value.to_i } else - symtab.cfg_arch.csr(idx.text_value) + symtab.design.arch.csr(idx.text_value) end end # @!macro type def type(symtab) - CsrType.new(csr_def(symtab), symtab.cfg_arch) + CsrType.new(csr_def(symtab), symtab.design) end def name(symtab) diff --git a/lib/idl/passes/gen_adoc.rb b/lib/idl/passes/gen_adoc.rb index 6213411fd..8eedfa4f8 100644 --- a/lib/idl/passes/gen_adoc.rb +++ b/lib/idl/passes/gen_adoc.rb @@ -273,7 +273,7 @@ def gen_adoc(indent = 0, indent_spaces: 2) after_name = [] after_name << "<#{template_arg_nodes.map { |t| t.gen_adoc(0, indent_spaces:)}.join(', ')}>" unless template_arg_nodes.empty? after_name << "pass:[(]#{arg_nodes.map { |a| a.gen_adoc(0, indent_spaces: ) }.join(', ')})" - "#{' '*indent}%%LINK%func;#{name};#{name}%%#{after_name.join ''}" + "#{' '*indent}" + link_to_udb_doc_idl_func(name) + "#{after_name.join ''}" end end @@ -301,10 +301,10 @@ def gen_adoc(indent = 0, indent_spaces: 2) if idx_text =~ /[0-9]+/ "#{' '*indent}#{csr_text}" else - if @cfg_arch.csr(csr_text).nil? + if @design.arch.csr(csr_text).nil? "#{' '*indent}#{csr_text}" else - "#{' '*indent}%%LINK%csr_field;#{idx_text}.#{@field_name};#{csr_text}%%" + "#{' '*indent}%%UDB_DOC_LINK%csr_field;#{idx_text}.#{@field_name};#{csr_text}%%" end end end @@ -324,10 +324,10 @@ def gen_adoc(indent = 0, indent_spaces: 2) # we don't have the symtab to map this to a csr name "#{' '*indent}#{csr_text}" else - if @cfg_arch.csr(csr_text).nil? + if @design.arch.csr(csr_text).nil? "#{' '*indent}#{csr_text}" else - "#{' '*indent}%%LINK%csr;#{idx_text};#{csr_text}%%" + "#{' '*indent}%%UDB_DOC_LINK%csr;#{idx_text};#{csr_text}%%" end end end diff --git a/lib/idl/passes/reachable_functions_unevaluated.rb b/lib/idl/passes/reachable_functions_unevaluated.rb index 22364b168..ed5ef129f 100644 --- a/lib/idl/passes/reachable_functions_unevaluated.rb +++ b/lib/idl/passes/reachable_functions_unevaluated.rb @@ -4,43 +4,43 @@ module Idl class AstNode - # @param cfg_arch [ConfiguredArchitecture] Architecture definition + # @param design [Design] The design # @return [Array] List of all functions that can be reached (via function calls) from this node, without considering value evaluation - def reachable_functions_unevaluated(cfg_arch) + def reachable_functions_unevaluated(design) children.reduce([]) do |list, e| - list.concat e.reachable_functions_unevaluated(cfg_arch) + list.concat e.reachable_functions_unevaluated(design) end.uniq(&:name) end end class FunctionCallExpressionAst - def reachable_functions_unevaluated(cfg_arch) + def reachable_functions_unevaluated(design) fns = [] if template? template_arg_nodes.each do |t| - fns.concat(t.reachable_functions_unevaluated(cfg_arch)) + fns.concat(t.reachable_functions_unevaluated(design)) end end arg_nodes.each do |a| - fns.concat(a.reachable_functions_unevaluated(cfg_arch)) + fns.concat(a.reachable_functions_unevaluated(design)) end - func_def_ast = cfg_arch.function(name) + func_def_ast = design.function(name) raise "No function '#{name}' found in Architecture def" if func_def_ast.nil? - fns += func_def_ast.reachable_functions_unevaluated(cfg_arch) + fns += func_def_ast.reachable_functions_unevaluated(design) fns.uniq(&:name) end end class FunctionDefAst - def reachable_functions_unevaluated(cfg_arch) + def reachable_functions_unevaluated(design) fns = [self] unless builtin? body.stmts.each do |stmt| - fns += stmt.reachable_functions_unevaluated(cfg_arch) + fns += stmt.reachable_functions_unevaluated(design) end end diff --git a/lib/idl/symbol_table.rb b/lib/idl/symbol_table.rb index 466183f9f..ca4c58a49 100644 --- a/lib/idl/symbol_table.rb +++ b/lib/idl/symbol_table.rb @@ -46,7 +46,7 @@ def decode_var? @decode_var end - # @param function_name [#to_s] A function name + # @param function_name [String] A function name # @return [Boolean] whether or not this variable is a function template argument from a call site for the function 'function_name' def template_value_for?(function_name) !@template_index.nil? && (function_name.to_s == @function_name) @@ -75,7 +75,7 @@ def value=(new_value) # scoped symbol table holding known symbols at a current point in parsing class SymbolTable - def cfg_arch = @cfg_arch + def design = @design # @return [Integer] 32 or 64, the XLEN in M-mode attr_reader :mxlen @@ -86,17 +86,15 @@ class DuplicateSymError < StandardError def hash return @frozen_hash unless @frozen_hash.nil? - [@scopes.hash, @cfg_arch.hash].hash + [@scopes.hash, @design.hash].hash end - def initialize(cfg_arch) - raise "Must provide cfg_arch" if cfg_arch.nil? - # TODO: XXX: Put this check back in when replaced by Design class. - # See https://github.com/riscv-software-src/riscv-unified-db/pull/371 - #raise "The cfg_arch must be a ConfiguredArchitecture but is a #{cfg_arch.class}" unless (cfg_arch.is_a?(ConfiguredArchitecture) || cfg_arch.is_a?(MockConfiguredArchitecture)) + def initialize(design) + raise "Must provide design" if design.nil? + raise "The design must be an IDesign but is a #{design.class}" unless design.is_a?(IDesign) - @cfg_arch = cfg_arch - @mxlen = cfg_arch.unconfigured? ? nil : cfg_arch.mxlen + @design = design + @mxlen = design.unconfigured? ? nil : design.mxlen @callstack = [nil] @scopes = [{ "X" => Var.new( @@ -117,7 +115,7 @@ def initialize(cfg_arch) ) }] - cfg_arch.params_with_value.each do |param_with_value| + design.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, qualifiers: [:const]) @@ -135,7 +133,7 @@ def initialize(cfg_arch) end # now add all parameters, even those not implemented - cfg_arch.params_without_value.each do |param| + design.params_without_value.each do |param| if param.exts.size == 1 add!(param.name, Var.new(param.name, param.idl_type.clone.make_const)) else @@ -161,7 +159,7 @@ def deep_freeze @scopes.freeze # set frozen_hash so that we can quickly compare symtabs - @frozen_hash = [@scopes.hash, @cfg_arch.hash].hash + @frozen_hash = [@scopes.hash, @design.hash].hash # set up the global clone that be used as a mutable table @global_clone_pool = [] @@ -170,7 +168,7 @@ def deep_freeze copy = SymbolTable.allocate copy.instance_variable_set(:@scopes, [@scopes[0]]) copy.instance_variable_set(:@callstack, [@callstack[0]]) - copy.instance_variable_set(:@cfg_arch, @cfg_arch) + copy.instance_variable_set(:@design, @design) copy.instance_variable_set(:@mxlen, @mxlen) copy.instance_variable_set(:@global_clone_pool, @global_clone_pool) copy.instance_variable_set(:@in_use, false) @@ -183,7 +181,7 @@ def deep_freeze # @return [String] inspection string def inspect - "SymbolTable[#{@cfg_arch.name}]#{frozen? ? ' (frozen)' : ''}" + "SymbolTable[#{@design.name}]#{frozen? ? ' (frozen)' : ''}" end # pushes a new scope @@ -346,7 +344,7 @@ def global_clone copy = SymbolTable.allocate copy.instance_variable_set(:@scopes, [@scopes[0]]) copy.instance_variable_set(:@callstack, [@callstack[0]]) - copy.instance_variable_set(:@cfg_arch, @cfg_arch) + copy.instance_variable_set(:@design, @design) copy.instance_variable_set(:@mxlen, @mxlen) copy.instance_variable_set(:@global_clone_pool, @global_clone_pool) copy.instance_variable_set(:@in_use, false) diff --git a/lib/idl/tests/helpers.rb b/lib/idl/tests/helpers.rb index 2d694e5b3..a7f0be497 100644 --- a/lib/idl/tests/helpers.rb +++ b/lib/idl/tests/helpers.rb @@ -1,6 +1,7 @@ # frozen_string_literal: true require "ostruct" +require_relative "../../idesign" # Extension mock that returns an extension name class Xmockension @@ -13,30 +14,31 @@ def initialize(name) XmockensionParameter = Struct.new(:name, :desc, :schema, :extra_validation, :exts, :type) XmockensionParameterWithValue = Struct.new(:name, :desc, :schema, :extra_validation, :exts, :value) -# ConfiguredArchitecture mock that knows about XLEN and extensions -class MockConfiguredArchitecture +# Design class mock that knows about XLEN and extensions +class MockDesign < IDesign + def initialize + super("mock") + end + + def mxlen = 64 def param_values = { "XLEN" => 32 } def params_with_value = [XmockensionParameterWithValue.new("XLEN", "mxlen", {"type" => "integer", "enum" => [32, 64]}, nil, nil, 32)] def params_without_value = [] - def params = [] - def extensions = [Xmockension.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 implemented_ext_vers = [Xmockension.new("I")] + def implemented_exception_codes = [OpenStruct.new(var: "ACode", num: 0), OpenStruct.new(var: "BCode", num: 1)] + def implemented_interrupt_codes = [OpenStruct.new(var: "CoolInterrupt", num: 1)] def fully_configured? = false def partially_configured? = true def unconfigured? = false - def name = "mock" - attr_accessor :global_ast end module TestMixin def setup - @cfg_arch = MockConfiguredArchitecture.new - @symtab = Idl::SymbolTable.new(@cfg_arch) + @design = MockDesign.new + @symtab = Idl::SymbolTable.new(@design) @compiler = Idl::Compiler.new end end diff --git a/lib/idl/tests/test_functions.rb b/lib/idl/tests/test_functions.rb index d079348ca..52eaab2d1 100644 --- a/lib/idl/tests/test_functions.rb +++ b/lib/idl/tests/test_functions.rb @@ -72,7 +72,7 @@ def test_that_reachable_raise_analysis_respects_transitive_known_values ast = @compiler.compile_file(path) ast.add_global_symbols(@symtab) @symtab.deep_freeze - @cfg_arch.global_ast = ast + @design.global_ast = ast ast.freeze_tree(@symtab) test_ast = ast.functions.select { |f| f.name == "test" }[0] @@ -137,7 +137,7 @@ def test_that_reachable_raise_analysis_respects_known_paths_down_an_unknown_path ast = @compiler.compile_file(path) ast.add_global_symbols(@symtab) @symtab.deep_freeze - @cfg_arch.global_ast = ast + @design.global_ast = ast ast.freeze_tree(@symtab) test_ast = ast.functions.select { |f| f.name == "test" }[0] diff --git a/lib/idl/type.rb b/lib/idl/type.rb index 07d2f46a9..bb9d6f87e 100644 --- a/lib/idl/type.rb +++ b/lib/idl/type.rb @@ -61,10 +61,10 @@ def qualify(qualifier) self end - def self.from_typename(type_name, cfg_arch) + def self.from_typename(type_name, design) case type_name when 'XReg' - return Type.new(:bits, width: cfg_arch.param_values['XLEN']) + return Type.new(:bits, width: design.param_values['XLEN']) when 'FReg' return Type.new(:freg, width: 32) when 'DReg' @@ -609,8 +609,8 @@ def clone class CsrType < Type attr_reader :csr - def initialize(csr, cfg_arch, qualifiers: []) - super(:csr, name: csr.name, csr: csr, width: csr.max_length(cfg_arch), qualifiers: qualifiers) + def initialize(csr, design, qualifiers: []) + super(:csr, name: csr.name, csr: csr, width: csr.max_length(design), qualifiers: qualifiers) end def fields diff --git a/lib/portfolio_design.rb b/lib/portfolio_design.rb new file mode 100644 index 000000000..fc11284ef --- /dev/null +++ b/lib/portfolio_design.rb @@ -0,0 +1,309 @@ +# frozen_string_literal: true + +# Combines knowledge of the architecture database with one or more portfolios (profile or certificate). +# +# Used in portfolio-based ERB templates to gather information about the "design". +# The "design" corresponds to the file being created by the ERB template and facilitates +# sharing ERB template fragments between different kinds of portfolios (mostly in the appendices). +# For example, a processor certificate model has one portfolio but a profile release has multiple portfolios +# but they both have just one PortfolioDesign object. + +require "ruby-prof" + +require_relative "architecture" +require_relative "design" +require_relative "arch_obj_models/portfolio" + +class PortfolioDesign < Design + # @return [PortfolioClass] Portfolio class for all the portfolios in this design + attr_reader :portfolio_class + + # @return [String] Kind of portfolio for all portfolios in this design + attr_reader :portfolio_kind + + # @return [String] Type of design suitable for human readers. + attr_reader :portfolio_design_type + + # Class methods + def self.profile_release_type = "Profile Release" + def self.proc_crd_type = "Certification Requirements Document" + def self.proc_ctp_type = "Certification Test Plan" + def self.portfolio_design_types = [profile_release_type, proc_crd_type, proc_ctp_type] + + # @param name [#to_s] The name of the portfolio design (i.e., backend filename without a suffix) + # @param arch [Architecture] The database of RISC-V standards + # @param portfolio_design_type [String] Type of portfolio design associated with this design + # @param mxlen [Integer] Comes from portfolio YAML "base" (either 32 or 64) + # @param portfolios [Array] Portfolios being converted to adoc + # @param portfolio_class [PortfolioClass] PortfolioClass for all the Portfolios + # @param overlay_path [String] Optional path to a directory that overlays the architecture + def initialize(name, arch, portfolio_design_type, portfolios, portfolio_class, overlay_path: nil) + raise ArgumentError, "arch must be an Architecture but is a #{arch.class}" unless arch.is_a?(Architecture) + raise ArgumentError, "portfolio_design_type of #{portfolio_design_type} unknown" unless PortfolioDesign.portfolio_design_types.include?(portfolio_design_type) + raise ArgumentError, "portfolios must be an Array but is a #{portfolios.class}" unless portfolios.is_a?(Array) + raise ArgumentError, "portfolio_class must be a PortfolioClass but is a #{portfolio_class.class}" unless portfolio_class.is_a?(PortfolioClass) + + @portfolio_design_type = portfolio_design_type + + # The PortfolioGroup has an Array inside it and forwards common Array methods to its internal Array. + # Can call @portfolio_grp.each or @portfolio_grp.map and they are handled by the normal Array methods. + @portfolio_grp = PortfolioGroup.new(portfolios) + + @portfolio_class = portfolio_class + @portfolio_kind = portfolios[0].kind + + max_base = portfolios.map(&:base).max + raise ArgumentError, "Calculated maximum base of #{max_base} across portfolios is not 32 or 64" unless max_base == 32 || max_base == 64 + + super(name, arch, max_base, overlay_path: overlay_path) + end + + # Returns a string representation of the object, suitable for debugging. + # @return [String] A string representation of the object. + def inspect = "PortfolioDesign##{name}" + + ################################## + # METHODS REQUIRED BY BASE CLASS # + ################################## + + # 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 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) + # + # Assume portfolios (profiles and certificates) don't need this ISA feature. + def multi_xlen_in_mode?(mode) = false + + # @return [Array] List of all parameters fully-constrained to one specific value + def params_with_value + return @params_with_value unless @params_with_value.nil? + + @params_with_value = [] + + in_scope_ext_reqs.each do |ext_req| + ext_req.extension.params.each do |param| + next unless param_values.key?(param.name) + + @params_with_value << ParameterWithValue.new(param, param_values[param.name]) + end + end + + @params_with_value + end + + # @return [Array] List of all available parameters not yet full-constrained to one specific value + def params_without_value + return @params_without_value unless @params_without_value.nil? + + @params_without_value = [] + arch.extensions.each do |ext| + ext.params.each do |param| + next if param_values.key?(param.name) + + @params_without_value << param + end + end + @params_without_value + end + + def implemented_ext_vers + # Only supported by fully-configured configurations and a portfolio corresponds to a + # partially-configured configuration. See the Config class for details. + raise "Not supported for portfolio #{name}" + end + + # @return [Array] List of all extensions that are prohibited. + # This includes extensions explicitly prohibited by the design + # and extensions that conflict with a mandatory extension. + # + # TODO: Assume there are none of these in a portfolio for now. + def prohibited_ext_reqs = [] + + # @overload ext?(ext_name) + # @param ext_name [#to_s] Extension name (case sensitive) + # @return [Boolean] True if the extension `name` must be 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 + # @return [Boolean] True if the extension `name` meeting `ext_version_requirements` must be implemented + # + # @example Checking extension presence with a version requirement + # PortfolioDesign.ext?(:S, ">= 1.12") + # @example Checking extension presence with multiple version requirements + # PortfolioDesign.ext?(:S, ">= 1.12", "< 1.15") + # @example Checking extension presence with a precise version requirement + # PortfolioDesign.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 = + mandatory_ext_reqs.any? do |ext_req| + if ext_version_requirements.empty? + ext_req.name == ext_name.to_s + else + requirement = ExtensionRequirement.new(ext_name, *ext_version_requirements, arch) + ext_req.satisfying_versions.all? do |ext_ver| + requirement.satisfied_by?(ext_ver) + end + end + end + + @ext_cache[[ext_name, ext_version_requirements]] = result + end + + # Given an adoc string, find names of CSR/Instruction/Extension enclosed in `monospace` + # and replace them with links to the relevant object page. + # See backend_helpers.rb for a definition of the proprietary link format. + # + # @param adoc [String] Asciidoc source + # @return [String] Asciidoc source, with link placeholders + def convert_monospace_to_links(adoc) + adoc.gsub(/`([\w.]+)`/) do |match| + name = Regexp.last_match(1) + csr_name, field_name = name.split(".") + csr = in_scope_csrs.find { |c| c.name == csr_name } + if !field_name.nil? && !csr.nil? && csr.field?(field_name) + link_to_udb_doc_csr_field(csr_name, field_name) + elsif !csr.nil? + link_to_udb_doc_csr(csr_name) + elsif in_scope_instructions.any? { |inst| inst.name == name } + link_to_udb_doc_inst(name) + elsif in_scope_extensions.any? { |ext| ext.name == name } + link_to_udb_doc_ext(name) + else + match + end + end + end + + # + # A Portfolio corresponds to a partially-configured design. + # See the Config class for details. + # + # @return [Boolean] True if all parameters are fully-constrained in the design + def fully_configured? = false + + # @return [Boolean] True if some parameters aren't fully-constrained yet in the design + def partially_configured? = true + + # @return [Boolean] True if all parameters aren't constrained at all in the design + def unconfigured? = false + + ##################################### + # METHODS HANDLED BY PortfolioGroup # + ##################################### + + # @return [Array] List of all mandatory extension requirements + def mandatory_ext_reqs = @portfolio_grp.mandatory_ext_reqs + + # @return [Hash] Fully-constrained parameter values (those with just one possible value for this design). + def param_values = @portfolio_grp.param_values + + # @return [Array] List of all mandatory or optional extensions referenced by this design. + def in_scope_extensions = @portfolio_grp.in_scope_extensions + + # @return [Array] List of all mandatory or optional extension requirements referenced by this design. + def in_scope_ext_reqs = @portfolio_grp.in_scope_ext_reqs + + # @return [Array] Sorted list of all instructions associated with extensions listed as + # mandatory or optional in portfolio. Uses instructions provided by the + # minimum version of the extension that meets the extension requirement. + # Factors in things like XLEN in design. + def in_scope_instructions = @portfolio_grp.in_scope_instructions(self) + + # @return [Array] Unsorted list of all CSRs associated with extensions listed as + # mandatory or optional in portfolio. Uses CSRs provided by the + # minimum version of the extension that meets the extension requirement. + # Factors in things like XLEN in design. + def in_scope_csrs = @portfolio_grp.in_scope_csrs(self) + + # @return [Array] Unsorted list of all in-scope exception codes. + def in_scope_exception_codes = @portfolio_grp.in_scope_exception_codes(self) + + # @return [Array] Unsorted list of all in-scope interrupt codes. + def in_scope_interrupt_codes = @portfolio_grp.in_scope_interrupt_codes(self) + + # @return [String] Given an extension +ext_name+, return the presence as a string. + # Returns the greatest presence string across all portfolios in this design. + # If the extension name isn't found in this design, return "-". + def extension_presence(ext_name) = @portfolio_grp.extension_presence(ext_name) + + # @return [Array] Sorted list of parameters specified by any extension in portfolio. + def all_in_scope_params = @portfolio_grp.all_in_scope_params + + # @param [ExtensionRequirement] + # @return [Array] Sorted list of extension parameters from portfolio for given extension. + def in_scope_params(ext_req) = @portfolio_grp.in_scope_params(ext_req) + + # @return [Array] Sorted list of parameters out of scope across all in scope extensions. + def all_out_of_scope_params = @portfolio_grp.all_out_of_scope_params + + # @param ext_name [String] Extension name + # @return [Array] Sorted list of parameters that are out of scope for named extension. + def out_of_scope_params(ext_name) = @portfolio_grp.out_of_scope_params(ext_name) + + # @param param [Parameter] + # @return [Array] Sorted list of all in-scope extensions that define this parameter + # in the database and the parameter is in-scope. + def all_in_scope_exts_with_param(param) = @portfolio_grp.all_in_scope_exts_with_param(param) + + # @param param [Parameter] + # @return [Array] List of all in-scope extensions that define this parameter in the + # database but the parameter is out-of-scope. + def all_in_scope_exts_without_param(param) = @portfolio_grp.all_in_scope_exts_without_param(param) + + ################# + # EXTRA METHODS # + ################# + + # @param extra_inputs [Hash] Any extra inputs to be passed to ERB template. + # @return [Hash] Hash of objects available to ERB templates and + # ERB fragments included in the main ERB template. + # Put this in a method so it can be easily overridden by subclasses. + def erb_env(extra_inputs = {}) + raise ArgumentError, "extra_inputs must be an Hash but is a #{extra_inputs.class}" unless extra_inputs.is_a?(Hash) + + h = { + arch: arch, + design: self, + portfolio_design: self, + portfolio_design_type: @portfolio_design_type, + portfolio_class: @portfolio_class, + portfolio_kind: @portfolio_kind, + portfolios: @portfolio_grp.portfolios + } + + h.merge!(extra_inputs) + end + + # Called from tasks.rake file to add standard set of objects available to ERB templates. + def init_erb_binding(erb_binding) + raise ArgumentError, "Expected Binding object but got #{erb_binding.class}" unless erb_binding.is_a?(Binding) + + erb_env.each do |key, obj| + erb_binding.local_variable_set(key, obj) + end + end + + # Include a partial ERB template into a full ERB template. + # + # @param template_path [String] Name of template file located in backends/portfolio/templates + # @param extra_inputs [Hash] Any extra inputs to be passed to ERB template. + # @return [String] Result of ERB evaluation of the template file + def include_erb(template_name, extra_inputs = {}) + template_pname = "portfolio/templates/#{template_name}" + puts "UPDATE: #{portfolio_design_type} processing ERB partial template '#{template_pname}'" + partial(template_pname, erb_env(extra_inputs)) + end +end diff --git a/lib/proc_cert_design.rb b/lib/proc_cert_design.rb new file mode 100644 index 000000000..2396b7130 --- /dev/null +++ b/lib/proc_cert_design.rb @@ -0,0 +1,75 @@ +# frozen_string_literal: true +# +# Inherits from PortfolioDesign and contains content shared by +# all processor certificate-based designs. + +require_relative "portfolio_design" + +class ProcCertDesign < PortfolioDesign + # @return [ProcCertModel] The processor certificate model object from the architecture database + attr_reader :proc_cert_model + + # @return [ProcCertClass] The processor certificate class object from the architecture database + attr_reader :proc_cert_class + + # @param name [#to_s] The name of the portfolio design (i.e., backend filename without a suffix) + # @param arch [Architecture] The database of RISC-V standards + # @param portfolio_design_type [String] Type of portfolio design associated with this design + # @param mxlen [Integer] Comes from portfolio YAML "base" (either 32 or 64) + # @param portfolios [Array] Portfolios being converted to adoc + # @param portfolio_class [PortfolioClass] PortfolioClass for all the Portfolios + # @param overlay_path [String] Optional path to a directory that overlays the architecture + def initialize(name, arch, portfolio_design_type, proc_cert_model, proc_cert_class, overlay_path: nil) + raise ArgumentError, "proc_cert_model must be a ProcCertModel" unless proc_cert_model.is_a?(ProcCertModel) + raise ArgumentError, "proc_cert_class must be a ProcCertClass" unless proc_cert_class.is_a?(ProcCertClass) + @proc_cert_model = proc_cert_model + @proc_cert_class = proc_cert_class + super(name, arch, portfolio_design_type, [proc_cert_model], proc_cert_class, overlay_path: overlay_path) + end + + # Returns a string representation of the object, suitable for debugging. + # @return [String] A string representation of the object. + def inspect = "ProcCertDesign##{name}" + + # @param extra_inputs [Array] Any extra inputs to be passed to ERB template. + # @return [Hash] Hash of objects to be used in ERB templates + # Add certificate-specific objects to the parent hash. + def erb_env(*extra_inputs) + raise ArgumentError, "extra_inputs must be an Array but is a #{extra_inputs.class}" unless extra_inputs.is_a?(Array) + + h = super # Call parent method with whatever args I got + + h[:proc_cert_design] = self + h[:proc_cert_model] = proc_cert_model + h[:proc_cert_class] = proc_cert_class + + h + end + + # Include a partial ERB template into a full ERB template. Can be either in + # the portfolio or proc_cert backends (but not both). + # + # @param template_path [String] Name of template file located in backends/portfolio/templates + # or in backends/proc_cert/templates + # @param extra_inputs [Hash] Any extra inputs to be passed to ERB template. + # @return [String] Result of ERB evaluation of the template file + def include_erb(template_name, extra_inputs = {}) + proc_cert_template_pname = "proc_cert/templates/#{template_name}" + proc_cert_template_path = Pathname.new($root / "backends" / proc_cert_template_pname) + + portfolio_template_pname = "portfolio/templates/#{template_name}" + portfolio_template_path = Pathname.new($root / "backends" / portfolio_template_pname) + + if proc_cert_template_path.exist? && portfolio_template_path.exist? + raise "Both #{proc_cert_template_pname} and #{portfolio_template_pname} exist. Need unique names." + elsif proc_cert_template_path.exist? + puts "UPDATE: #{portfolio_design_type} processing ERB partial template '#{proc_cert_template_pname}'" + partial(proc_cert_template_pname, erb_env(extra_inputs)) + elsif portfolio_template_path.exist? + puts "UPDATE: #{portfolio_design_type} processing ERB partial template '#{portfolio_template_pname}'" + partial(portfolio_template_pname, erb_env(extra_inputs)) + else + raise "Can't find file #{template_name} in either #{proc_cert_template_pname} or #{portfolio_template_pname}." + end + end +end diff --git a/lib/template_helpers.rb b/lib/template_helpers.rb deleted file mode 100644 index f42ef9603..000000000 --- a/lib/template_helpers.rb +++ /dev/null @@ -1,84 +0,0 @@ -# frozen_string_literal: true - -# At this point, we insert a placeholder since it will be up -# to the backend to create a specific link. - -require "erb" -require "pathname" - -# collection of functions that can be used inside ERB templates -module TemplateHelpers - # Insert a hyperlink to an extension. - # @param name [#to_s] Name of the extension - def link_to_ext(name) - "%%LINK%ext;#{name};#{name}%%" - end - - # Insert a hyperlink to an extension parameter. - # @param ext_name [#to_s] Name of the extension - # @param param_name [#to_s] Name of the parameter - def link_to_ext_param(ext_name, param_name) - "<>" - end - - # Insert a hyperlink to an instruction. - # @param name [#to_s] Name of the instruction - def link_to_inst(name) - "%%LINK%inst;#{name};#{name}%%" - end - - # Insert a hyperlink to a CSR. - # @param name [#to_s] Name of the CSR - def link_to_csr(name) - "%%LINK%csr;#{name};#{name}%%" - end - - # Insert a hyperlink to a CSR field. - # @param csr_name [#to_s] Name of the CSR - # @param field_name [#to_s] Name of the CSR field - def link_to_csr_field(csr_name, field_name) - "%%LINK%csr_field;#{csr_name}.#{field_name};#{csr_name}.#{field_name}%%" - end - - # Insert anchor to an extension. - # @param name [#to_s] Name of the extension - def anchor_for_ext(name) - "[[ext-#{name.gsub(".", "_")}-def]]" - end - - # Insert anchor to an extension parameter. - # @param ext_name [#to_s] Name of the extension - # @param param_name [#to_s] Name of the parameter - def anchor_for_ext_param(ext_name, param_name) - "[[ext-#{ext_name.gsub(".", "_")}-param-#{param_name}-def]]" - end - - # Insert anchor to an instruction. - # @param name [#to_s] Name of the instruction - def anchor_for_inst(name) - "[[inst-#{name.gsub(".", "_")}-def]]" - end - - # Insert anchor to a CSR. - # @param name [#to_s] Name of the CSR - def anchor_for_csr(name) - "[[csr-#{name.gsub(".", "_")}-def]]" - end - - # Insert anchor to a CSR field. - # @param csr_name [#to_s] Name of the CSR - # @param field_name [#to_s] Name of the CSR field - def anchor_for_csr_field(csr_name, field_name) - "[[csr_field-#{csr_name.gsub(".", "_")}-#{field_name.gsub(".", "_")}-def]]" - end - - def partial(template_path, locals = {}) - template_path = Pathname.new($root / "backends" / "common_templates" / template_path) - raise ArgumentError, "Template '#{template_path} not found" unless template_path.exist? - - erb = ERB.new(template_path.read, trim_mode: "-") - erb.filename = template_path.realpath.to_s - - erb.result(OpenStruct.new(locals).instance_eval { binding }) - end -end diff --git a/lib/test/test_backend_helpers.rb b/lib/test/test_backend_helpers.rb new file mode 100644 index 000000000..0d63840f2 --- /dev/null +++ b/lib/test/test_backend_helpers.rb @@ -0,0 +1,152 @@ +# frozen_string_literal: true + +require "English" +require "minitest/autorun" +require "backend_helpers" + +include TemplateHelpers + +$root ||= (Pathname.new(__FILE__) / ".." / ".." / "..").realpath + +class TestBackendHelpers < Minitest::Test + def test_ext + assert_equal("%%UDB_DOC_LINK%ext;foo;foo%%", link_to_udb_doc_ext("foo")) + assert_equal("[#udb:doc:ext:foo]", anchor_for_udb_doc_ext("foo")) + assert_equal("%%UDB_DOC_LINK%ext;fo_o;fo.o%%", link_to_udb_doc_ext("fo.o")) + assert_equal("[#udb:doc:ext:fo_o]", anchor_for_udb_doc_ext("fo.o")) + end + + def test_ext_param + assert_equal("%%UDB_DOC_LINK%ext_param;foo.bar;zort%%", link_to_udb_doc_ext_param("foo","bar","zort")) + assert_equal("[#udb:doc:ext_param:foo:bar]", anchor_for_udb_doc_ext_param("foo","bar")) + assert_equal("%%UDB_DOC_LINK%ext_param;fo_o.bar;fluffy%%", link_to_udb_doc_ext_param("fo.o","bar","fluffy")) + assert_equal("[#udb:doc:ext_param:fo_o:bar]", anchor_for_udb_doc_ext_param("fo.o","bar")) + assert_raises(ArgumentError) { link_to_udb_doc_ext_param("foo","ba.r","fluffy") } + assert_raises(ArgumentError) { anchor_for_udb_doc_ext_param("foo","ba.r") } + end + + def test_inst + assert_equal("%%UDB_DOC_LINK%inst;foo;foo%%", link_to_udb_doc_inst("foo")) + assert_equal("[#udb:doc:inst:foo]", anchor_for_udb_doc_inst("foo")) + assert_equal("%%UDB_DOC_LINK%inst;fo_o;fo.o%%", link_to_udb_doc_inst("fo.o")) + assert_equal("[#udb:doc:inst:fo_o]", anchor_for_udb_doc_inst("fo.o")) + end + + def test_csr + assert_equal("%%UDB_DOC_LINK%csr;foo;foo%%", link_to_udb_doc_csr("foo")) + assert_equal("[#udb:doc:csr:foo]", anchor_for_udb_doc_csr("foo")) + assert_equal("%%UDB_DOC_LINK%csr;fo_o;fo.o%%", link_to_udb_doc_csr("fo.o")) + assert_equal("[#udb:doc:csr:fo_o]", anchor_for_udb_doc_csr("fo.o")) + end + + def test_csr_field + assert_equal("%%UDB_DOC_LINK%csr_field;foo.bar;foo.bar%%", link_to_udb_doc_csr_field("foo","bar")) + assert_equal("[#udb:doc:csr_field:foo:bar]", anchor_for_udb_doc_csr_field("foo","bar")) + assert_equal("%%UDB_DOC_LINK%csr_field;fo_o.ba_r;fo.o.ba.r%%", link_to_udb_doc_csr_field("fo.o","ba.r")) + assert_equal("[#udb:doc:csr_field:fo_o:ba_r]", anchor_for_udb_doc_csr_field("fo.o","ba.r")) + end + + def test_cov_pt + assert_equal("%%UDB_DOC_COV_PT_LINK%sep;foo_and_bar;foo&bar%%", link_to_udb_doc_cov_pt("sep", "foo&bar")) + assert_equal("[[udb:doc:cov_pt:sep:foo]]", anchor_for_udb_doc_cov_pt("sep", "foo")) + assert_equal("%%UDB_DOC_COV_PT_LINK%combo;fo_o;fo.o%%", link_to_udb_doc_cov_pt("combo", "fo.o")) + assert_equal("[[udb:doc:cov_pt:combo:fo_o]]", anchor_for_udb_doc_cov_pt("combo", "fo.o")) + assert_raises(ArgumentError) { link_to_udb_doc_cov_pt("bad-org-value","abc") } + end + + def test_idl_func + assert_equal("%%UDB_DOC_LINK%func;foo;foo%%", link_to_udb_doc_idl_func("foo")) + assert_equal("[#udb:doc:func:foo]", anchor_for_udb_doc_idl_func("foo")) + assert_equal("%%UDB_DOC_LINK%func;fo_o;fo.o%%", link_to_udb_doc_idl_func("fo.o")) + assert_equal("[#udb:doc:func:fo_o]", anchor_for_udb_doc_idl_func("fo.o")) + end + + def test_idl_code + assert_equal("%%IDL_CODE_LINK%inst;foo.bar;foo.bar%%", link_into_idl_inst_code("foo","bar")) + assert_equal("[#idl:code:inst:foo:bar]", anchor_inside_idl_inst_code("foo","bar")) + assert_equal("%%IDL_CODE_LINK%inst;fo_o.ba_r;fo.o.ba.r%%", link_into_idl_inst_code("fo.o","ba.r")) + assert_equal("[#idl:code:inst:fo_o:ba_r]", anchor_inside_idl_inst_code("fo.o","ba.r")) + end + +end + +class TestAsciidocUtils < Minitest::Test + def test_resolve_links_ext + assert_equal("<>", AsciidocUtils.resolve_links("%%UDB_DOC_LINK%ext;foo;bar%%")) + assert_equal("<>", AsciidocUtils.resolve_links(link_to_udb_doc_ext("foo"))) + end + + def test_resolve_links_ext_param + assert_equal("<>", AsciidocUtils.resolve_links("%%UDB_DOC_LINK%ext_param;foo.bar;zort%%")) + assert_equal("<>", AsciidocUtils.resolve_links(link_to_udb_doc_ext_param("foo","bar","bob"))) + end + + def test_resolve_links_inst + assert_equal("<>", AsciidocUtils.resolve_links("%%UDB_DOC_LINK%inst;foo;bar%%")) + assert_equal("<>", AsciidocUtils.resolve_links(link_to_udb_doc_inst("foo"))) + end + + def test_resolve_links_csr + assert_equal("<>", AsciidocUtils.resolve_links("%%UDB_DOC_LINK%csr;foo;bar%%")) + assert_equal("<>", AsciidocUtils.resolve_links(link_to_udb_doc_csr("foo"))) + end + + def test_resolve_links_csr_field + assert_equal("<>", AsciidocUtils.resolve_links("%%UDB_DOC_LINK%csr_field;foo.bar;zort%%")) + assert_equal("<>", AsciidocUtils.resolve_links(link_to_udb_doc_csr_field("foo","bar"))) + end + + def test_resolve_links_func + assert_equal("<>", AsciidocUtils.resolve_links("%%UDB_DOC_LINK%func;foo;bar%%")) + assert_equal("<>", AsciidocUtils.resolve_links(link_to_udb_doc_idl_func("foo"))) + end + + def test_resolve_links_cov_pt + assert_equal("<>", AsciidocUtils.resolve_links("%%UDB_DOC_COV_PT_LINK%sep;foo;bar%%")) + assert_equal("<>", AsciidocUtils.resolve_links(link_to_udb_doc_cov_pt("sep", "foo"))) + assert_equal("<>", AsciidocUtils.resolve_links("%%UDB_DOC_COV_PT_LINK%combo;foo;bar%%")) + assert_equal("<>", AsciidocUtils.resolve_links(link_to_udb_doc_cov_pt("combo", "foo"))) + end + + def test_resolve_links_idl_code + assert_equal("<>", AsciidocUtils.resolve_links("%%IDL_CODE_LINK%inst;foo.bar;zort%%")) + assert_equal("<>", AsciidocUtils.resolve_links(link_into_idl_inst_code("foo","bar"))) + end +end + +class TestAntoraUtils < Minitest::Test + def test_resolve_links_ext + assert_equal("xref:exts:foo.adoc#udb:doc:ext:foo[bar]", AntoraUtils.resolve_links("%%UDB_DOC_LINK%ext;foo;bar%%")) + assert_equal("xref:exts:foo.adoc#udb:doc:ext:foo[foo]", AntoraUtils.resolve_links(link_to_udb_doc_ext("foo"))) + end + + def test_resolve_links_ext_param + assert_equal("xref:exts:foo.adoc#udb:doc:ext_param:foo:bar[[zort]]", AntoraUtils.resolve_links("%%UDB_DOC_LINK%ext_param;foo.bar;[zort]%%")) + assert_equal("xref:exts:foo.adoc#udb:doc:ext_param:foo:bar[bob]", AntoraUtils.resolve_links(link_to_udb_doc_ext_param("foo","bar","bob"))) + end + + def test_resolve_links_inst + assert_equal("xref:insts:foo.adoc#udb:doc:inst:foo[bar]", AntoraUtils.resolve_links("%%UDB_DOC_LINK%inst;foo;bar%%")) + assert_equal("xref:insts:foo.adoc#udb:doc:inst:foo[foo]", AntoraUtils.resolve_links(link_to_udb_doc_inst("foo"))) + end + + def test_resolve_links_csr + assert_equal("xref:csrs:foo.adoc#udb:doc:csr:foo[bar]", AntoraUtils.resolve_links("%%UDB_DOC_LINK%csr;foo;bar%%")) + assert_equal("xref:csrs:foo.adoc#udb:doc:csr:foo[foo]", AntoraUtils.resolve_links(link_to_udb_doc_csr("foo"))) + end + + def test_resolve_links_csr_field + assert_equal("xref:csrs:foo.adoc#udb:doc:csr_field:foo:bar[zort]", AntoraUtils.resolve_links("%%UDB_DOC_LINK%csr_field;foo.bar;zort%%")) + assert_equal("xref:csrs:foo.adoc#udb:doc:csr_field:foo:bar[foo.bar]", AntoraUtils.resolve_links(link_to_udb_doc_csr_field("foo","bar"))) + end + + def test_resolve_links_func + assert_equal("xref:funcs:funcs.adoc#udb:doc:func:foo[bar]", AntoraUtils.resolve_links("%%UDB_DOC_LINK%func;foo;bar%%")) + assert_equal("xref:funcs:funcs.adoc#udb:doc:func:foo[foo]", AntoraUtils.resolve_links(link_to_udb_doc_idl_func("foo"))) + end + + def test_resolve_links_idl_code + assert_equal("xref:insts:foo.adoc#idl:code:inst:foo:bar[zort]", AntoraUtils.resolve_links("%%IDL_CODE_LINK%inst;foo.bar;zort%%")) + assert_equal("xref:insts:foo.adoc#idl:code:inst:foo:bar[foo.bar]", AntoraUtils.resolve_links(link_into_idl_inst_code("foo","bar"))) + end +end diff --git a/lib/version.rb b/lib/version.rb index 6151c810a..725ebb968 100644 --- a/lib/version.rb +++ b/lib/version.rb @@ -188,7 +188,7 @@ def satisfied_by?(version, ext) matching_ver = ext.versions.find { |v| v.version_spec == v_spec } raise "Can't find version?" if matching_ver.nil? - matching_ver.compatible?(ExtensionVersion.new(ext.name, v_spec.to_s, ext.cfg_arch)) + matching_ver.compatible?(ExtensionVersion.new(ext.name, v_spec.to_s, ext.arch)) end end end diff --git a/lib/yaml_resolver.py b/lib/yaml_resolver.py index 110c271bf..e63b74548 100644 --- a/lib/yaml_resolver.py +++ b/lib/yaml_resolver.py @@ -520,12 +520,15 @@ def resolve_file( resolved_obj["$source"] = os.path.join(args.arch_dir, rel_path) if do_checks and ("$schema" in resolved_obj): - schema = _get_schema(resolved_obj["$schema"]) + schema_uri = resolved_obj["$schema"] + schema_relpath = schema_uri.split("#")[0] + schema = _get_schema(schema_uri) try: schema.validate(instance=resolved_obj) except ValidationError as e: print(f"JSON Schema Validation Error for {rel_path}:") print(best_match(schema.iter_errors(resolved_obj)).message) + print(f" Used schema {schema_relpath}") exit(1) write_yaml(resolved_path, resolved_obj) diff --git a/schemas/csr_schema.json b/schemas/csr_schema.json index 6ef1790a7..bbceb6b6f 100644 --- a/schemas/csr_schema.json +++ b/schemas/csr_schema.json @@ -105,6 +105,12 @@ } ], "description": "Extension(s) that affect the definition of the field beyond the extension (or base) the field is originally defined in" + }, + "cert_coverage_points": { + "$ref": "schema_defs.json#/$defs/cert_coverage_points" + }, + "cert_test_procedures": { + "$ref": "schema_defs.json#/$defs/cert_test_procedures" } }, @@ -269,6 +275,12 @@ "$source": { "description": "Path to the source file this definition came from; used by downstream tooling -- not expected to be in handwritten files", "type": "string" + }, + "cert_coverage_points": { + "$ref": "schema_defs.json#/$defs/cert_coverage_points" + }, + "cert_test_procedures": { + "$ref": "schema_defs.json#/$defs/cert_test_procedures" } }, "additionalProperties": false, diff --git a/schemas/ext_schema.json b/schemas/ext_schema.json index 40d6d485c..6d7a2131a 100644 --- a/schemas/ext_schema.json +++ b/schemas/ext_schema.json @@ -290,6 +290,12 @@ "$source": { "type": "string", "description": "Source file where this extension was defined" + }, + "cert_coverage_points": { + "$ref": "schema_defs.json#/$defs/cert_coverage_points" + }, + "cert_test_procedures": { + "$ref": "schema_defs.json#/$defs/cert_test_procedures" } }, "additionalProperties": false diff --git a/schemas/inst_schema.json b/schemas/inst_schema.json index 637543623..fe2c655f0 100644 --- a/schemas/inst_schema.json +++ b/schemas/inst_schema.json @@ -257,6 +257,12 @@ "type": "string", "description": "Functional description of the instruction using Sail" }, + "cert_coverage_points": { + "$ref": "schema_defs.json#/$defs/cert_coverage_points" + }, + "cert_test_procedures": { + "$ref": "schema_defs.json#/$defs/cert_test_procedures" + }, "assembly": { "type": "string", "description": "Assembly format of the instruction. Can use decode variables" diff --git a/schemas/cert_class_schema.json b/schemas/proc_cert_class_schema.json similarity index 93% rename from schemas/cert_class_schema.json rename to schemas/proc_cert_class_schema.json index a816c4333..c174d8d6f 100644 --- a/schemas/cert_class_schema.json +++ b/schemas/proc_cert_class_schema.json @@ -7,11 +7,11 @@ "properties": { "$schema": { "type": "string", - "const": "cert_class_schema.json#" + "const": "proc_cert_class_schema.json#" }, "kind": { "type": "string", - "const": "Processor CRD" + "const": "Processor Certificate Class" }, "name": { "type": "string", diff --git a/schemas/cert_model_schema.json b/schemas/proc_cert_model_schema.json similarity index 85% rename from schemas/cert_model_schema.json rename to schemas/proc_cert_model_schema.json index 65383a4e2..6a4eed33a 100644 --- a/schemas/cert_model_schema.json +++ b/schemas/proc_cert_model_schema.json @@ -2,20 +2,20 @@ "$schema": "http://json-schema.org/draft-07/schema#", "type": "object", - "required": ["$schema", "kind", "name", "long_name"], + "required": ["$schema", "kind", "name", "long_name", "base"], "additionalProperties": false, "properties": { "$inherits": { "oneOf": [ { "type": "string", - "pattern": "^(profile|certificate_model)/.*\\.yaml#.*" + "pattern": "^(profile|proc_cert_model)/.*\\.yaml#.*" }, { "type": "array", "items": { "type": "string", - "pattern": "^(profile|certificate_model)/.*\\.yaml#.*" + "pattern": "^(profile|proc_cert_model)/.*\\.yaml#.*" }, "uniqueItems": true } @@ -25,13 +25,13 @@ "oneOf": [ { "type": "string", - "pattern": "^(profile|certificate_model)/.*\\.yaml#.*" + "pattern": "^(profile|proc_cert_model)/.*\\.yaml#.*" }, { "type": "array", "items": { "type": "string", - "pattern": "^(profile|certificate_model)/.*\\.yaml#.*" + "pattern": "^(profile|proc_cert_model)/.*\\.yaml#.*" }, "uniqueItems": true } @@ -41,13 +41,13 @@ "oneOf": [ { "type": "string", - "pattern": "^(profile|certificate_model)/.*\\.yaml#.*" + "pattern": "^(profile|proc_cert_model)/.*\\.yaml#.*" }, { "type": "array", "items": { "type": "string", - "pattern": "^(profile|certificate_model)/.*\\.yaml#.*" + "pattern": "^(profile|proc_cert_model)/.*\\.yaml#.*" }, "uniqueItems": true } @@ -55,11 +55,11 @@ }, "$schema": { "type": "string", - "const": "cert_model_schema.json#" + "const": "proc_cert_model_schema.json#" }, "kind": { "type": "string", - "const": "certificate model" + "const": "Processor Certificate Model" }, "name": { "type": "string", @@ -75,7 +75,7 @@ "properties": { "$ref": { "type": "string", - "pattern": "^certificate_class/[A-Z][a-zA-Z0-9_]*\\.yaml#" + "pattern": "^proc_cert_class/[A-Z][a-zA-Z0-9_]*\\.yaml#" } }, "description": "Reference to the class this model belongs to" @@ -123,7 +123,7 @@ "properties": { "$ref": { "type": "string", - "pattern": "^profile_release|certificate_model/[A-Z][a-zA-Z0-9_]*\\.yaml#" + "pattern": "^profile_release|proc_cert_model/[A-Z][a-zA-Z0-9_]*\\.yaml#" } }, "additionalProperties": false @@ -148,13 +148,13 @@ "oneOf": [ { "type": "string", - "pattern": "^(profile|certificate_model)/.*\\.yaml#.*" + "pattern": "^(profile|proc_cert_model)/.*\\.yaml#.*" }, { "type": "array", "items": { "type": "string", - "pattern": "^(profile|certificate_model)/.*\\.yaml#.*" + "pattern": "^(profile|proc_cert_model)/.*\\.yaml#.*" }, "uniqueItems": true } @@ -164,13 +164,13 @@ "oneOf": [ { "type": "string", - "pattern": "^(profile|certificate_model)/.*\\.yaml#.*" + "pattern": "^(profile|proc_cert_model)/.*\\.yaml#.*" }, { "type": "array", "items": { "type": "string", - "pattern": "^(profile|certificate_model)/.*\\.yaml#.*" + "pattern": "^(profile|proc_cert_model)/.*\\.yaml#.*" }, "uniqueItems": true } diff --git a/schemas/profile_class_schema.json b/schemas/profile_class_schema.json index 663b65cb3..00a9e4a22 100644 --- a/schemas/profile_class_schema.json +++ b/schemas/profile_class_schema.json @@ -10,7 +10,7 @@ }, "kind": { "type": "string", - "const": "profile class" + "const": "Profile Class" }, "processor_kind": { "type": "string", diff --git a/schemas/profile_release_schema.json b/schemas/profile_release_schema.json index 9d784c6d3..0c09411e2 100644 --- a/schemas/profile_release_schema.json +++ b/schemas/profile_release_schema.json @@ -10,7 +10,7 @@ }, "kind": { "type": "string", - "const": "profile release" + "const": "Profile Release" }, "name": { "type": "string", diff --git a/schemas/profile_schema.json b/schemas/profile_schema.json index 40e6f2e88..e0f36b436 100644 --- a/schemas/profile_schema.json +++ b/schemas/profile_schema.json @@ -2,7 +2,7 @@ "$schema": "http://json-schema.org/draft-07/schema#", "type": "object", - "required": ["$schema", "kind", "name"], + "required": ["$schema", "kind", "name", "base"], "properties": { "$schema": { "type": "string", @@ -10,11 +10,21 @@ }, "kind": { "type": "string", - "const": "profile" + "const": "Profile" }, "name": { "type": "string", "description": "Name (database key) of this Profile" + }, + "base": { + "type": "integer", + "description": "32 for RV32I or 64 for RV64I" + }, + "cert_coverage_points": { + "$ref": "schema_defs.json#/$defs/cert_coverage_points" + }, + "cert_test_procedures": { + "$ref": "schema_defs.json#/$defs/cert_test_procedures" } } } diff --git a/schemas/schema_defs.json b/schemas/schema_defs.json index a5ee77adc..db86c92e7 100644 --- a/schemas/schema_defs.json +++ b/schemas/schema_defs.json @@ -267,6 +267,74 @@ "$ref": "#/$defs/when_condition" } } + }, + "cert_coverage_points": { + "description": "Architecturally visible behaviors requiring validation by certification tests", + "type": "array", + "required": ["id", "name", "links", "description"], + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "doc_links": { + "description": "Link to UDB documentation, ISA manual, Sail code, or IDL code", + "type": "array", + "items": { + "type": "string" + } + }, + "description": { + "type": "string" + } + }, + "additionalProperties": false + }, + "cert_test_procedures": { + "description": "Procedure test must follow to test certification coverage points", + "type": "array", + "required": ["id", "name", "description", "coverage_points"], + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "description": { + "type": "string" + }, + "coverage_points": { + "type": "array", + "description": "List of certification coverage point IDs to be validated", + "items": { + "type": "string" + } + }, + "steps": { + "description": "List of steps to be followed in order by test", + "type": "array", + "items": { + "type": "object", + "required": ["name", "description"], + "properties": { + "name": { + "type": "string" + }, + "description": { + "type": "string" + }, + "note": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } } } }