Skip to content
Merged
Show file tree
Hide file tree
Changes from 38 commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
7f82b46
Add simple Docker environment variable
AFOliveira Dec 13, 2024
7141a9c
Fix errors due to incorrect parsing of VM
AFOliveira Dec 18, 2024
6e45c3b
First Refactor to pytest
AFOliveira Dec 19, 2024
ac04c28
Allow 16 bit instructions for C extension
AFOliveira Dec 19, 2024
f1b8613
Revert bad parsing
AFOliveira Dec 19, 2024
e3e7456
Allow only one value
AFOliveira Dec 19, 2024
4355eb0
Use AsmString instead of name
AFOliveira Dec 19, 2024
8492581
Small Refactor on parsing.py
AFOliveira Dec 22, 2024
3bd1d2c
refactor to do unit tests
AFOliveira Dec 22, 2024
6b9fdda
refactor to file name
AFOliveira Dec 22, 2024
831a10b
Modify to have seveal Unit tests instead of just one
AFOliveira Dec 23, 2024
655e1d6
Clean up and code reorganization
AFOliveira Dec 23, 2024
d61fb3b
Ensure it is not pseudo
AFOliveira Dec 23, 2024
5992124
Skip aq/rl instructions
AFOliveira Dec 23, 2024
c2e6e92
add pytest to requirements
AFOliveira Dec 23, 2024
7c9c65b
add LLVM path as environment variable
AFOliveira Dec 27, 2024
3178538
remove and ignor python cache
AFOliveira Dec 27, 2024
11d4c60
Add LLVM test to the Rakefile
AFOliveira Dec 27, 2024
2185239
Optimizations to test logic and modify test order
AFOliveira Dec 27, 2024
64a7525
Merge branch 'main' into AFOliveira/LLVM
AFOliveira Jan 8, 2025
9b9bce5
Add prerequisites syntax
Jan 9, 2025
3939224
Fix merge conflict
Jan 9, 2025
daae5d5
Fix pre-commit related issues
Jan 10, 2025
450c65b
Merge branch 'main' into AFOliveira/LLVM
AFOliveira Jan 14, 2025
d9e45b3
Add LLVM tblgen to regress.yaml && change Rakefile for new changes
AFOliveira Jan 17, 2025
0fa5bdd
Fix caching
AFOliveira Jan 17, 2025
56412ac
Fix caching
AFOliveira Jan 17, 2025
b2748e0
Fix caching
AFOliveira Jan 17, 2025
6968919
Add dependencie for smoke test
AFOliveira Jan 17, 2025
25aa928
Change logic for LLVM's path
AFOliveira Jan 17, 2025
5ee8496
Change CI logic for LLVM
AFOliveira Jan 20, 2025
92f9c99
Add cache and update paths
AFOliveira Jan 20, 2025
34e769e
Add corner case when implementation of LLVM does not need to follow t…
AFOliveira Jan 20, 2025
b3c523c
Merge branch 'main' into AFOliveira/LLVM
AFOliveira Jan 20, 2025
15b12f3
Merge branch 'main' into AFOliveira/LLVM
AFOliveira Jan 22, 2025
af0acae
Work around for FENCE. ISA and compiler should treat it differently
AFOliveira Jan 27, 2025
b1e0ee6
Set CM instruction length 16 bit instead of 32
AFOliveira Jan 27, 2025
f99e556
Merge branch 'main' into AFOliveira/LLVM
AFOliveira Jan 27, 2025
4510bed
merged
AFOliveira Apr 11, 2025
7b1697f
Merge remote-tracking branch 'origin/main' into AFOliveira/LLVM
dhower-qc Apr 18, 2025
7bfe623
Merge branch 'main' into AFOliveira/LLVM
AFOliveira Apr 21, 2025
b2f102e
Add LICENSE compliant to UDB native files.
AFOliveira Apr 24, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 62 additions & 0 deletions .github/workflows/regress.yml
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,24 @@ jobs:
- uses: actions/setup-python@v5
- uses: pre-commit/[email protected]
regress-smoke:
needs: build-llvm
runs-on: ubuntu-latest
env:
SINGULARITY: 1
steps:
- name: Clone Github Repo Action
uses: actions/checkout@v4
- name: Get current LLVM submodule commit SHA
id: get-llvm-sha
run: echo "LLVM_SHA=$(git ls-tree HEAD ext/llvm-project | awk '{print $3}')" >> $GITHUB_ENV
- name: Restore cache RISC-V JSON
id: cache-riscv
uses: actions/cache@v4
with:
path: ext/llvm-project/riscv.json
key: ${{ runner.os }}-riscv-json-${{ env.LLVM_SHA }}
restore-keys: |
${{ runner.os }}-riscv-json-
- name: Setup apptainer
uses: eWaterCycle/[email protected]
- name: Get container from cache
Expand Down Expand Up @@ -185,3 +197,53 @@ jobs:
run: ./bin/build_container
- name: Generate extension PDF
run: ./do gen:profile[MockProfileRelease]

build-llvm:
runs-on: ubuntu-latest

steps:
- name: Check out repository (no submodules, shallow fetch)
uses: actions/checkout@v4
with:
submodules: false
fetch-depth: 1
- name: Get current LLVM submodule commit SHA
id: get-llvm-sha
run: echo "LLVM_SHA=$(git ls-tree HEAD ext/llvm-project | awk '{print $3}')" >> $GITHUB_ENV
- name: Cache RISC-V JSON
id: cache-riscv
uses: actions/cache@v4
with:
path: ext/llvm-project/riscv.json
key: ${{ runner.os }}-riscv-json-${{ env.LLVM_SHA }}
restore-keys: |
${{ runner.os }}-riscv-json-
- name: Initialize LLVM submodule (shallow + sparse)
if: ${{ steps.cache-riscv.outputs.cache-hit != 'true' }}
run: |
git submodule sync --recursive
git submodule update --init --recursive --depth=1 ext/llvm-project

- name: Check for required directories and files
if: ${{ steps.cache-riscv.outputs.cache-hit != 'true' }}
run: |
ls -l ext/llvm-project/llvm/include
ls -l ext/llvm-project/llvm/lib/Target/RISCV
ls -l ext/llvm-project/llvm/lib/Target/RISCV/RISCV.td
- name: Configure and build llvm-tblgen
if: ${{ steps.cache-riscv.outputs.cache-hit != 'true' }}
run: |
cmake -S ext/llvm-project/llvm -B ext/llvm-project/build -DCMAKE_BUILD_TYPE=Release
cmake --build ext/llvm-project/build --target llvm-tblgen
- name: Generate RISC-V JSON
if: ${{ steps.cache-riscv.outputs.cache-hit != 'true' }}
run: |
chmod +x ./ext/llvm-project/build/bin/llvm-tblgen
./ext/llvm-project/build/bin/llvm-tblgen \
-I ext/llvm-project/llvm/include \
-I ext/llvm-project/llvm/lib/Target/RISCV \
ext/llvm-project/llvm/lib/Target/RISCV/RISCV.td \
--dump-json \
-o ext/llvm-project/riscv.json
- name: Show riscv.json output
run: ls -l ext/llvm-project/riscv.json
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,7 @@ gen
node_modules
_site
images
__pycache__/
*.pyc
.pytest_cache/
*.log
4 changes: 4 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,7 @@
[submodule "ext/riscv-isa-manual"]
path = ext/riscv-isa-manual
url = https://github.com/riscv/riscv-isa-manual
[submodule "ext/llvm-project"]
path = ext/llvm-project
url = https://github.com/llvm/llvm-project.git
branch = main
Empty file modified .pre-commit-config.yaml
100644 → 100755
Empty file.
16 changes: 10 additions & 6 deletions Rakefile
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,15 @@ namespace :serve do
end

namespace :test do

# "Run the cross-validation against LLVM"
task :llvm do
begin
sh "#{$root}/.home/.venv/bin/python3 -m pytest ext/auto-inst/test_parsing.py -v"
rescue => e
raise unless e.message.include?("status (5)") # don't fail on skipped tests
end
end
# "Run the IDL compiler test suite"
task :idl_compiler do
t = Minitest::TestTask.new(:lib_test)
Expand Down Expand Up @@ -294,12 +303,7 @@ namespace :test do

These are basic but fast-running tests to check the database and tools
DESC
task :smoke do
Rake::Task["test:idl_compiler"].invoke
Rake::Task["test:lib"].invoke
Rake::Task["test:schema"].invoke
Rake::Task["test:idl"].invoke
end
task :smoke => ["test:idl_compiler", "test:lib", "test:schema", "test:idl", "test:llvm"]

desc <<~DESC
Run the regression tests
Expand Down
274 changes: 274 additions & 0 deletions ext/auto-inst/parsing.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,274 @@
import os
import re
import yaml
from pathlib import Path
import pytest

yaml_instructions = {}
REPO_DIRECTORY = None


def safe_get(data, key, default=""):
"""Safely get a value from a dictionary, return default if not found or error."""
try:
if isinstance(data, dict):
return data.get(key, default)
return default
except:
return default


def get_json_path():
"""
Resolves the path to riscv.json in the repository.
Returns the Path object if file exists, otherwise skips the test.
"""
# Print current working directory and script location for debugging
cwd = Path.cwd()
script_dir = Path(__file__).parent.resolve()
print(f"Current working directory: {cwd}")
print(f"Script directory: {script_dir}")

# Try to find the repository root
repo_root = os.environ.get("GITHUB_WORKSPACE", cwd)
repo_root = Path(repo_root)

llvm_json_path = repo_root / "ext" / "llvm-project" / "riscv.json"
print(f"Looking for riscv.json at: {llvm_json_path}")

if not llvm_json_path.is_file():
print(f"\nNo 'riscv.json' found at {llvm_json_path}.")
print("Tests will be skipped.\n")
pytest.skip("riscv.json does not exist in the repository at the expected path.")

return llvm_json_path


def get_yaml_directory():
return "arch/inst/"


def load_inherited_variable(var_path, repo_dir):
"""Load variable definition from an inherited YAML file."""
try:
path, anchor = var_path.split("#")
if anchor.startswith("/"):
anchor = anchor[1:]

full_path = os.path.join(repo_dir, path)

if not os.path.exists(full_path):
print(f"Warning: Inherited file not found: {full_path}")
return None

with open(full_path) as f:
data = yaml.safe_load(f)

for key in anchor.split("/"):
if key in data:
data = data[key]
else:
print(f"Warning: Anchor path {anchor} not found in {path}")
return None

return data
except Exception as e:
print(f"Error loading inherited variable {var_path}: {str(e)}")
return None


def resolve_variable_definition(var, repo_dir):
"""Resolve variable definition, handling inheritance if needed."""
if "location" in var:
return var
elif "$inherits" in var:
print(f"Warning: Failed to resolve inheritance for variable: {var}")
return None


def parse_location(loc_str):
"""Parse location string that may contain multiple ranges."""
if not loc_str:
return []

loc_str = str(loc_str).strip()
ranges = []

for range_str in loc_str.split("|"):
range_str = range_str.strip()
if "-" in range_str:
high, low = map(int, range_str.split("-"))
ranges.append((high, low))
else:
try:
val = int(range_str)
ranges.append((val, val))
except ValueError:
print(f"Warning: Invalid location format: {range_str}")
continue

return ranges


def load_yaml_encoding(instr_name):
"""Load YAML encoding data for an instruction."""
candidates = set()
lower_name = instr_name.lower()
candidates.add(lower_name)
candidates.add(lower_name.replace("_", "."))

yaml_file_path = None
for cand in candidates:
if cand in yaml_instructions:
yaml_category = yaml_instructions[cand]
yaml_file_path = os.path.join(REPO_DIRECTORY, yaml_category, cand + ".yaml")
if os.path.isfile(yaml_file_path):
break
else:
yaml_file_path = None

if not yaml_file_path or not os.path.isfile(yaml_file_path):
return None, None

with open(yaml_file_path) as yf:
ydata = yaml.safe_load(yf)

encoding = safe_get(ydata, "encoding", {})
yaml_match = safe_get(encoding, "match", None)
yaml_vars = safe_get(encoding, "variables", [])

return yaml_match, yaml_vars


def compare_yaml_json_encoding(
instr_name, yaml_match, yaml_vars, json_encoding_str, repo_dir
):
"""Compare the YAML encoding with the JSON encoding."""
if not yaml_match:
return ["No YAML match field available for comparison."]
if not json_encoding_str:
return ["No JSON encoding available for comparison."]

expected_length = (
16 if instr_name.lower().startswith(("c_", "c.", "cm_", "cm.")) else 32
)

yaml_pattern_str = yaml_match.replace("-", ".")
if len(yaml_pattern_str) != expected_length:
return [
f"YAML match pattern length is {len(yaml_pattern_str)}, expected {expected_length}. Cannot compare properly."
]

yaml_var_positions = {}
for var in yaml_vars or []:
resolved_var = resolve_variable_definition(var, repo_dir)
if not resolved_var or "location" not in resolved_var:
print(
f"Warning: Could not resolve variable definition for {var.get('name', 'unknown')}"
)
continue

ranges = parse_location(resolved_var["location"])
if ranges:
yaml_var_positions[var["name"]] = ranges

tokens = re.findall(r"(?:[01]|[A-Za-z0-9]+(?:\[\d+\]|\[\?\])?)", json_encoding_str)
json_bits = []
bit_index = expected_length - 1
for t in tokens:
json_bits.append((bit_index, t))
bit_index -= 1

if bit_index != -1:
return [
f"JSON encoding does not appear to be {expected_length} bits. Ends at bit {bit_index+1}."
]

normalized_json_bits = []
for pos, tt in json_bits:
if re.match(r"vm\[[^\]]*\]", tt):
tt = "vm"
normalized_json_bits.append((pos, tt))
json_bits = normalized_json_bits

differences = []

for b in range(expected_length):
yaml_bit = yaml_pattern_str[expected_length - 1 - b]
token = [tt for (pos, tt) in json_bits if pos == b]
if not token:
differences.append(f"Bit {b}: No corresponding JSON bit found.")
continue
json_bit_str = token[0]

if yaml_bit in ["0", "1"]:
if json_bit_str not in ["0", "1"]:
differences.append(
f"Bit {b}: YAML expects fixed bit '{yaml_bit}' but JSON has '{json_bit_str}'"
)
elif json_bit_str != yaml_bit:
differences.append(
f"Bit {b}: YAML expects '{yaml_bit}' but JSON has '{json_bit_str}'"
)
else:
if json_bit_str in ["0", "1"]:
differences.append(
f"Bit {b}: YAML variable bit but JSON is fixed '{json_bit_str}'"
)

for var_name, ranges in yaml_var_positions.items():
for high, low in ranges:
if high >= expected_length or low < 0:
differences.append(
f"Variable {var_name}: location {high}-{low} is out of range for {expected_length}-bit instruction."
)
continue

json_var_fields = []
for bb in range(low, high + 1):
token = [tt for (pos, tt) in json_bits if pos == bb]
if token:
json_var_fields.append(token[0])
else:
json_var_fields.append("?")

field_names = set(
re.findall(
r"([A-Za-z0-9]+)(?:\[\d+\]|\[\?\])?", " ".join(json_var_fields)
)
)
if len(field_names) == 0:
differences.append(
f"Variable {var_name}: No corresponding field found in JSON bits {high}-{low}"
)
elif len(field_names) > 1:
differences.append(
f"Variable {var_name}: Multiple fields {field_names} found in JSON for bits {high}-{low}"
)

return differences


def get_yaml_instructions(repo_directory):
"""Recursively find all YAML files in the repository and load their encodings."""
global yaml_instructions, REPO_DIRECTORY
REPO_DIRECTORY = repo_directory
yaml_instructions = {}

for root, _, files in os.walk(repo_directory):
for file in files:
if file.endswith(".yaml"):
instr_name = os.path.splitext(file)[0]
relative_path = os.path.relpath(root, repo_directory)
yaml_instructions[instr_name.lower()] = relative_path

instructions_with_encodings = {}
for instr_name_lower, path in yaml_instructions.items():
yaml_match, yaml_vars = load_yaml_encoding(instr_name_lower)
instructions_with_encodings[instr_name_lower] = {
"category": path,
"yaml_match": yaml_match,
"yaml_vars": yaml_vars,
}

return instructions_with_encodings
Loading
Loading