blob: 717a7b4ad760f38cfa017e83b5788eec4aa273c4 [file] [log] [blame]
"""
Copyright 2019 Google LLC
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
Parse processor-specific CSR description YAML file and generate a CSR test file.
This test code will utilize every CSR instruction, writing values to the CSR
and then using a prediction function to calculate a reference value that will
be written into another register and compared against the value actually stored
in the CSR at this point, allowing for the test to self-check in order to
determine success or failure.
"""
"""
To install the bitstring library:
1) sudo apt-get install python3-bitstring OR
2) pip install bitstring
"""
import sys
import yaml
import argparse
import random
import copy
try:
from bitstring import BitArray as bitarray
except ImportError as e:
logging.error("Please install bitstring package: sudo apt-get install python3-bitstring")
sys.exit(1)
"""
Defines the test's success/failure values, one of which will be written to
the chosen signature address to indicate the test's result.
"""
TEST_RESULT = 1
TEST_PASS = 0
TEST_FAIL = 1
def get_csr_map(csr_file, xlen):
"""
Parses the YAML file containing CSR descriptions.
Args:
csr_file: The CSR YAML file.
xlen: The current RISC-V ISA bit length.
Returns:
A dictionary contining mappings for each CSR, of the form:
{ csr_name : [csr_address, csr_val_bitarray, csr_write_mask_bitarray, csr_read_mask_bitarray] }
"""
rv_string = "rv{}".format(str(xlen))
csrs = {}
with open(csr_file, "r") as c:
csr_description = yaml.safe_load(c)
for csr_dict in csr_description:
csr_name = csr_dict.get("csr")
csr_address = csr_dict.get("address")
assert(rv_string in csr_dict), "The {} CSR must be configured for rv{}".format(csr_name, str(rv))
csr_value = bitarray(uintbe=0, length=xlen)
csr_write_mask = []
csr_read_mask = bitarray(uintbe=0, length=xlen)
csr_field_list = csr_dict.get(rv_string)
for csr_field_detail_dict in csr_field_list:
field_type = csr_field_detail_dict.get("type")
field_val = csr_field_detail_dict.get("reset_val")
field_msb = csr_field_detail_dict.get("msb")
field_lsb = csr_field_detail_dict.get("lsb")
field_size = field_msb - field_lsb + 1
if field_type != "WPRI":
val_bitarray = bitarray(uint=field_val, length=field_size)
mask_bitarray = bitarray(uint=1, length=1) * field_size
start_pos = xlen - 1 - field_msb
end_pos = xlen - 1 - field_lsb
csr_read_mask.overwrite(mask_bitarray, xlen - 1 - field_msb)
csr_value.overwrite(val_bitarray, xlen - 1 - field_msb)
access = True if field_type == "R" else False
csr_write_mask.append([mask_bitarray, (start_pos, end_pos), access])
csrs.update({csr_name : [csr_address, csr_value, csr_write_mask, csr_read_mask]})
return csrs
def get_rs1_val(iteration, xlen):
"""
Calculates and returns the 3 test RS1 values that will be used
to exercise the CSR.
Args:
iteration: Integer between 0 and 2 inclusive, indicates which
test value to return.
xlen: The currnet RISC-V ISA bit length.
Returns:
A bitarray encoding the value that will be written to the CSR to test it.
Will be one of 3 values:
1) 0xa5a5...
2) 0x5a5a...
3) A randomly generated number
"""
if iteration == 0:
return bitarray(hex=f"0x{'a5'*int(xlen/8)}")
elif iteration == 1:
return bitarray(hex=f"0x{'5a'*int(xlen/8)}")
elif iteration == 2:
val = bitarray(uint=0, length=xlen)
# Must randomize all 32 bits, due to randomization library limitations
for i in range(32):
bit = random.randint(0, 1)
val.set(bit, i)
return val
def csr_write(val, csr_val, csr_write_mask):
"""
Performs a CSR write.
Args:
val: A bitarray containing the value to be written.
csr_val: A bitarray containing the current CSR value.
csr_write_mask: A bitarray containing the CSR's mask.
"""
for bitslice in csr_write_mask:
read_only = bitslice[2]
start_index = bitslice[1][0]
end_index = bitslice[1][1]
length = end_index - start_index + 1
mask_val = bitslice[0]
# only write if not read only
if not read_only:
val_slice = val[start_index:end_index+1]
csr_val.overwrite(mask_val & val_slice, start_index)
"""
CSR Read:
Reads the given CSR, after applying the bitmask
"""
def csr_read(csr_val, csr_read_mask):
"""
Performs a CSR read.
Args:
csr_val: A bitarray containing the current CSR value.
csr_read_mask: A bitarray containing the CSR's read mask.
Returns:
A bitarray of the logical AND of csr_val and csr_read_mask.
"""
return csr_val & csr_read_mask
def predict_csr_val(csr_op, rs1_val, csr_val, csr_write_mask, csr_read_mask):
"""
Predicts the CSR reference value, based on the current CSR operation.
Args:
csr_op: A string of the CSR operation being performed.
rs1_val: A bitarray containing the value to be written to the CSR.
csr_val: A bitarray containing the current value of the CSR.
csr_write_mask: A bitarray containing the CSR's write mask.
csr_read_mask: A bitarray containing the CSR's read mask
Returns:
A hexadecimal string of the predicted CSR value.
"""
prediction = None
# create a zero bitarray to zero extend immediates
zero = bitarray(uint=0, length=csr_val.len - 5)
if csr_op == 'csrrw':
prediction = csr_read(csr_val, csr_read_mask)
csr_write(rs1_val, csr_val, csr_write_mask)
elif csr_op == 'csrrs':
prediction = csr_read(csr_val, csr_read_mask)
csr_write(rs1_val | prediction, csr_val, csr_write_mask)
elif csr_op == 'csrrc':
prediction = csr_read(csr_val, csr_read_mask)
csr_write((~rs1_val) & prediction, csr_val, csr_write_mask)
elif csr_op == 'csrrwi':
prediction = csr_read(csr_val, csr_read_mask)
zero.append(rs1_val[-5:])
csr_write(zero, csr_val, csr_write_mask)
elif csr_op == 'csrrsi':
prediction = csr_read(csr_val, csr_read_mask)
zero.append(rs1_val[-5:])
csr_write(zero | prediction, csr_val, csr_write_mask)
elif csr_op == 'csrrci':
prediction = csr_read(csr_val, csr_read_mask)
zero.append(rs1_val[-5:])
csr_write((~zero) & prediction, csr_val, csr_write_mask)
return f"0x{prediction.hex}"
def gen_setup(test_file):
"""
Generates the setup code for the CSR test.
Args:
test_file: the file containing the generated assembly code.
"""
test_file.write(f".macro init\n")
test_file.write(f".endm\n")
test_file.write(f".section .text.init\n")
test_file.write(f".globl _start\n")
test_file.write(f".option norvc\n")
test_file.write(f"_start:\n")
def gen_csr_test_fail(test_file, end_addr):
"""
Generates code to handle a test failure.
This code consists of writing 1 to the GP register in an infinite loop.
The testbench will poll this register at the end of the test to detect failure.
Args:
test_file: the file containing the generated assembly test code.
end_addr: address that should be written to at end of test
"""
test_file.write(f"csr_fail:\n")
test_file.write(f"\tli x1, {TEST_FAIL}\n")
test_file.write(f"\tslli x1, x1, 8\n")
test_file.write(f"\taddi x1, x1, {TEST_RESULT}\n")
test_file.write(f"\tli x2, {end_addr}\n")
test_file.write(f"\tsw x1, 0(x2)\n")
test_file.write(f"\tj csr_fail\n")
def gen_csr_test_pass(test_file, end_addr):
"""
Generates code to handle test success.
This code consists of writing 2 to the GP register in an infinite loop.
The testbench will poll this register at the end of the test to detect success.
Args:
test_file: the file containing the generated assembly test code.
end_addr: address that should be written to at end of test
"""
test_file.write(f"csr_pass:\n")
test_file.write(f"\tli x1, {TEST_PASS}\n")
test_file.write(f"\tslli x1, x1, 8\n")
test_file.write(f"\taddi x1, x1, {TEST_RESULT}\n")
test_file.write(f"\tli x2, {end_addr}\n")
test_file.write(f"\tsw x1, 0(x2)\n")
test_file.write(f"\tj csr_pass\n")
def gen_csr_instr(original_csr_map, csr_instructions, xlen,
iterations, out, end_signature_addr):
"""
Uses the information in the map produced by get_csr_map() to generate
test CSR instructions operating on the generated random values.
Args:
original_csr_map: The dictionary containing CSR mappings generated by get_csr_map()
csr_instructions: A list of all supported CSR instructions in string form.
xlen: The RISC-V ISA bit length.
iterations: Indicates how many randomized test files will be generated.
out: A string containing the directory path that the tests will be generated in.
end_signature_addr: The address the test should write to upon terminating
Returns:
No explicit return value, but will write the randomized assembly test code
to the specified number of files.
"""
for i in range(iterations):
# pick two GPRs at random to act as source and destination registers
# for CSR operations
csr_map = copy.deepcopy(original_csr_map)
source_reg, dest_reg = [f"x{i}" for i in random.sample(range(1, 16), 2)]
csr_list = list(csr_map.keys())
with open(f"{out}/riscv_csr_test_{i}.S", "w") as csr_test_file:
gen_setup(csr_test_file)
for csr in csr_list:
csr_address, csr_val, csr_write_mask, csr_read_mask = csr_map.get(csr)
csr_test_file.write(f"\t# {csr}\n")
for op in csr_instructions:
for i in range(3):
# hex string
rand_rs1_val = get_rs1_val(i, xlen)
# I type CSR instruction
first_li = ""
if op[-1] == "i":
imm = rand_rs1_val[-5:]
csr_inst = f"\t{op} {dest_reg}, {csr_address}, 0b{imm.bin}\n"
imm_val = bitarray(uint=0, length=xlen-5)
imm_val.append(imm)
predict_li = (f"\tli {source_reg}, "
f"{predict_csr_val(op, imm_val, csr_val, csr_write_mask, csr_read_mask)}\n")
else:
first_li = f"\tli {source_reg}, 0x{rand_rs1_val.hex}\n"
csr_inst = f"\t{op} {dest_reg}, {csr_address}, {source_reg}\n"
predict_li = (f"\tli {source_reg}, "
f"{predict_csr_val(op, rand_rs1_val, csr_val, csr_write_mask, csr_read_mask)}\n")
branch_check = f"\tbne {source_reg}, {dest_reg}, csr_fail\n"
csr_test_file.write(first_li)
csr_test_file.write(csr_inst)
csr_test_file.write(predict_li)
csr_test_file.write(branch_check)
"""
We must hardcode in one final CSR check, as the value that has last
been written to the CSR has not been tested.
"""
if csr == csr_list[-1] and op == csr_instructions[-1] and i == 2:
final_csr_read = f"\tcsrr {dest_reg}, {csr_address}\n"
csrrs_read_mask = bitarray(uint=0, length=xlen)
final_li = (f"\tli {source_reg}, "
f"{predict_csr_val('csrrs', csrrs_read_mask, csr_val, csr_write_mask, csr_read_mask)}\n")
final_branch_check = f"\tbne {source_reg}, {dest_reg}, csr_fail\n"
csr_test_file.write(final_csr_read)
csr_test_file.write(final_li)
csr_test_file.write(final_branch_check)
gen_csr_test_pass(csr_test_file, end_signature_addr)
gen_csr_test_fail(csr_test_file, end_signature_addr)
"""
Define command line arguments.
"""
parser = argparse.ArgumentParser()
parser.add_argument("--csr_file", type=str, default="yaml/csr_template.yaml",
help="The YAML file contating descriptions of all processor supported CSRs")
parser.add_argument("--xlen", type=int, default=32,
help="Specify the ISA width, e.g. 32 or 64 or 128")
parser.add_argument("--iterations", type=int, default=1,
help="Specify how many tests to be generated")
parser.add_argument("--out", type=str, default="./",
help="Specify output directory")
parser.add_argument("--end_signature_addr", type=str, default="0",
help="Address that should be written to at end of this test")
args = parser.parse_args()
"""
A list containing all supported CSR instructions.
"""
csr_ops = ['csrrw', 'csrrs', 'csrrc', 'csrrwi', 'csrrsi', 'csrrci']
gen_csr_instr(get_csr_map(args.csr_file, args.xlen),
csr_ops, args.xlen, args.iterations, args.out,
args.end_signature_addr)