use std::collections::HashSet;
use crate::{
dslx,
dslx_bridge::{BridgeBuilder, StructMemberData},
ir_value::IrFormatPreference,
IrValue, XlsynthError,
};
const LOGIC_ALIAS_SUFFIX: &str = "_t";
const ENUM_ALIAS_SUFFIX: &str = "_t";
const STRUCT_ALIAS_SUFFIX: &str = "_t";
const TYPE_ALIAS_SUFFIX: &str = "_t";
pub struct SvBridgeBuilder {
lines: Vec<String>,
defined: HashSet<String>,
}
fn camel_to_snake(name: &str) -> String {
let mut snake = String::new();
for (i, c) in name.chars().enumerate() {
if c.is_uppercase() && i > 0 {
snake.push('_');
}
snake.push(c.to_ascii_lowercase());
}
snake
}
fn screaming_snake_to_upper_camel(name: &str) -> String {
name.split('_')
.filter(|s| !s.is_empty())
.map(|s| {
let mut chars = s.chars();
chars
.next()
.map(|c| c.to_ascii_uppercase().to_string())
.unwrap_or_default()
+ &chars.as_str().to_ascii_lowercase()
})
.collect()
}
fn is_screaming_snake_case(name: &str) -> bool {
name.chars().all(|c| {
if c.is_ascii_alphabetic() {
c.is_ascii_uppercase()
} else {
true
}
})
}
fn make_bit_span_suffix(bit_count: usize) -> String {
assert!(bit_count > 0);
if bit_count == 1 {
"".to_string()
} else {
format!(" [{}:0]", bit_count - 1)
}
}
fn import_to_pkg_name(import: &dslx::Import) -> String {
let subject = import.get_subject();
assert!(
subject.len() > 0,
"import subjects always have at least one token"
);
format!("{}_sv_pkg", subject.last().unwrap())
}
fn enum_name_to_sv(dslx_name: &str) -> String {
format!("{}{}", camel_to_snake(dslx_name), ENUM_ALIAS_SUFFIX)
}
fn struct_name_to_sv(dslx_name: &str) -> String {
format!("{}{}", camel_to_snake(dslx_name), STRUCT_ALIAS_SUFFIX)
}
fn get_extern_type_ref(
type_annotation: &dslx::TypeAnnotation,
concrete_ty: &dslx::Type,
) -> Option<String> {
if let Some(type_ref_type_annotation) = type_annotation.to_type_ref_type_annotation() {
let type_ref = type_ref_type_annotation.get_type_ref();
let type_definition: dslx::TypeDefinition = type_ref.get_type_definition();
if let Some(colon_ref) = type_definition.to_colon_ref() {
if let Some(import) = colon_ref.resolve_import_subject() {
let pkg_name = import_to_pkg_name(&import);
let extern_ref =
convert_extern_type(&pkg_name, Some(&colon_ref.get_attr()), concrete_ty, None)
.unwrap();
return Some(extern_ref);
}
}
}
None
}
enum MatchableDslxType {
BitsLike {
is_signed: bool,
bit_count: usize,
},
Enum(dslx::EnumDef),
Struct(dslx::StructDef),
Array {
element_ty: Box<DslxType>,
size: usize,
},
}
struct DslxType {
ty: dslx::Type,
matchable_ty: MatchableDslxType,
}
fn dslx_type_to_matchable(ty: &dslx::Type) -> Result<DslxType, XlsynthError> {
if let Some((is_signed, bit_count)) = ty.is_bits_like() {
Ok(DslxType {
ty: ty.clone(),
matchable_ty: MatchableDslxType::BitsLike {
is_signed,
bit_count,
},
})
} else if ty.is_enum() {
Ok(DslxType {
ty: ty.clone(),
matchable_ty: MatchableDslxType::Enum(ty.get_enum_def().unwrap()),
})
} else if ty.is_struct() {
Ok(DslxType {
ty: ty.clone(),
matchable_ty: MatchableDslxType::Struct(ty.get_struct_def().unwrap()),
})
} else if ty.is_array() {
Ok(DslxType {
ty: ty.clone(),
matchable_ty: MatchableDslxType::Array {
element_ty: Box::new(dslx_type_to_matchable(&ty.get_array_element_type())?),
size: ty.get_array_size(),
},
})
} else {
Err(XlsynthError(format!(
"Unsupported type for conversion from DSLX to matchable type: {:?}",
ty.to_string()?
)))
}
}
fn make_array_span_suffix(array_sizes: Vec<usize>) -> String {
let mut suffix_parts = Vec::new();
for array_size in array_sizes.iter() {
suffix_parts.push(format!(" [{}:0]", array_size - 1));
}
suffix_parts.join("")
}
fn convert_type(ty: &dslx::Type, array_sizes: Option<Vec<usize>>) -> Result<String, XlsynthError> {
let matchable_ty = dslx_type_to_matchable(ty)?;
match matchable_ty.matchable_ty {
MatchableDslxType::BitsLike {
is_signed,
bit_count,
} => {
let leader = if is_signed { "logic signed" } else { "logic" };
Ok(format!(
"{}{}{}",
leader,
make_array_span_suffix(array_sizes.unwrap_or(vec![])),
make_bit_span_suffix(bit_count)
))
}
MatchableDslxType::Enum(enum_def) => Ok(format!(
"{}{}",
enum_name_to_sv(&enum_def.get_identifier()),
make_array_span_suffix(array_sizes.unwrap_or(vec![]))
)),
MatchableDslxType::Struct(struct_def) => Ok(format!(
"{}{}",
struct_name_to_sv(&struct_def.get_identifier()),
make_array_span_suffix(array_sizes.unwrap_or(vec![]))
)),
MatchableDslxType::Array { element_ty, size } => {
let mut array_sizes = array_sizes.unwrap_or(vec![]);
array_sizes.push(size);
convert_type(&element_ty.ty, Some(array_sizes))
}
}
}
fn convert_extern_type(
pkg_name: &str,
attr: Option<&str>,
ty: &dslx::Type,
array_sizes: Option<Vec<usize>>,
) -> Result<String, XlsynthError> {
let matchable_ty = dslx_type_to_matchable(ty)?;
match matchable_ty.matchable_ty {
MatchableDslxType::BitsLike { .. } => {
if let Some(attr) = attr {
let attr_sv = format!("{}{}", camel_to_snake(attr), LOGIC_ALIAS_SUFFIX);
Ok(format!("{pkg_name}::{attr_sv}"))
} else {
convert_type(ty, array_sizes)
}
}
MatchableDslxType::Enum(enum_def) => Ok(format!(
"{pkg_name}::{ty_name}",
ty_name = enum_name_to_sv(&enum_def.get_identifier())
)),
MatchableDslxType::Struct(struct_def) => Ok(format!(
"{pkg_name}::{ty_name}",
ty_name = struct_name_to_sv(&struct_def.get_identifier())
)),
MatchableDslxType::Array { element_ty, size } => {
let mut array_sizes = array_sizes.unwrap_or(vec![]);
array_sizes.push(size);
Ok(convert_extern_type(
pkg_name,
None,
&element_ty.ty,
Some(array_sizes),
)?)
}
}
}
impl SvBridgeBuilder {
pub fn new() -> Self {
Self {
lines: vec![],
defined: HashSet::new(),
}
}
pub fn build(&self) -> String {
self.lines.join("\n")
}
fn define_or_error(&mut self, name: &str, ctx: &str) -> Result<(), XlsynthError> {
let inserted = self.defined.insert(name.to_string());
if inserted {
Ok(())
} else {
Err(XlsynthError(format!(
"Building SV; name collision detected for SV name in generated module namespace: `{name}` context: {ctx}"
)))
}
}
fn enum_member_name_to_sv(dslx_name: &str) -> String {
if is_screaming_snake_case(dslx_name) {
screaming_snake_to_upper_camel(dslx_name)
} else {
dslx_name.to_string()
}
}
}
impl BridgeBuilder for SvBridgeBuilder {
fn start_module(&mut self, _module_name: &str) -> Result<(), XlsynthError> {
Ok(())
}
fn end_module(&mut self, _module_name: &str) -> Result<(), XlsynthError> {
Ok(())
}
fn add_enum_def(
&mut self,
dslx_name: &str,
is_signed: bool,
underlying_bit_count: usize,
members: &[(String, IrValue)],
) -> Result<(), XlsynthError> {
let mut lines = vec![];
let sv_name = enum_name_to_sv(dslx_name);
lines.push(format!(
"typedef enum logic{} {{",
make_bit_span_suffix(underlying_bit_count)
));
let ctx = format!("DSLX enum `{dslx_name}`");
for (i, (member_name, member_value)) in members.iter().enumerate() {
let format = if is_signed {
IrFormatPreference::SignedDecimal
} else {
IrFormatPreference::UnsignedDecimal
};
let member_value_str = member_value.to_string_fmt(format)?;
let digits = member_value_str.split(':').nth(1).expect("split success");
let maybe_comma = if i < members.len() - 1 { "," } else { "" };
let sv_member_name = Self::enum_member_name_to_sv(member_name);
self.define_or_error(&sv_member_name, &ctx)?;
lines.push(format!(
" {} = {}'d{}{}",
sv_member_name, underlying_bit_count, digits, maybe_comma
));
}
lines.push(format!("}} {};\n", sv_name));
self.lines.push(lines.join("\n"));
Ok(())
}
fn add_struct_def(
&mut self,
dslx_name: &str,
members: &[StructMemberData],
) -> Result<(), XlsynthError> {
let mut lines = vec![];
lines.push(format!("typedef struct packed {{"));
for member in members {
let member_name = &member.name;
let member_concrete_ty = &member.concrete_type;
let member_annotated_ty = &member.type_annotation;
if let Some(extern_ref) = get_extern_type_ref(member_annotated_ty, member_concrete_ty) {
lines.push(format!(" {} {};", extern_ref, member_name));
continue;
}
if let Some(type_ref_type_annotation) =
member_annotated_ty.to_type_ref_type_annotation()
{
let type_ref = type_ref_type_annotation.get_type_ref();
let type_def = type_ref.get_type_definition();
if let Some(type_alias) = type_def.to_type_alias() {
let sv_type_name = struct_name_to_sv(&type_alias.get_identifier());
lines.push(format!(" {} {};", sv_type_name, member_name));
continue;
}
}
let member_sv_ty = convert_type(member_concrete_ty, None)?;
lines.push(format!(" {} {};", member_sv_ty, member_name));
}
lines.push(format!("}} {};\n", struct_name_to_sv(dslx_name)));
self.lines.push(lines.join("\n"));
Ok(())
}
fn add_alias(
&mut self,
dslx_name: &str,
type_annotation: &dslx::TypeAnnotation,
ty: &dslx::Type,
) -> Result<(), XlsynthError> {
let sv_name = format!("{}{}", camel_to_snake(dslx_name), TYPE_ALIAS_SUFFIX);
if let Some(extern_ref) = get_extern_type_ref(type_annotation, ty) {
self.lines
.push(format!("typedef {} {};\n", extern_ref, sv_name));
} else {
let sv_ty = convert_type(ty, None)?;
self.lines.push(format!("typedef {} {};\n", sv_ty, sv_name));
}
Ok(())
}
fn add_constant(
&mut self,
name: &str,
_constant_def: &dslx::ConstantDef,
ty: &dslx::Type,
ir_value: &IrValue,
) -> Result<(), XlsynthError> {
if let Some((is_signed, bit_count)) = ty.is_bits_like() {
let sv_name = if is_screaming_snake_case(name) {
screaming_snake_to_upper_camel(name)
} else {
name.to_string()
};
let hex_prefix = if is_signed { "sh" } else { "h" };
let hex_digits = ir_value
.to_string_fmt_no_prefix(IrFormatPreference::ZeroPaddedHex)?
.replace("_", "");
let value_str = format!("{}'{}{}", bit_count, hex_prefix, hex_digits,);
self.lines.push(format!(
"localparam bit {signedness} [{}:0] {name} = {value_str};\n",
bit_count - 1,
name = sv_name,
signedness = if is_signed { "signed" } else { "unsigned" }
));
Ok(())
} else {
log::warn!("Unsupported constant type: {:?}", ir_value);
Ok(())
}
}
}
#[cfg(test)]
mod tests {
use std::str::FromStr;
use crate::dslx_bridge::{convert_imported_module, convert_leaf_module};
use super::*;
fn simple_convert_for_test(dslx: &str) -> Result<String, XlsynthError> {
let mut import_data = dslx::ImportData::default();
let path = std::path::PathBuf::from_str("/memfile/my_module.x").unwrap();
let mut builder = SvBridgeBuilder::new();
convert_leaf_module(&mut import_data, dslx, &path, &mut builder)?;
Ok(builder.build())
}
#[test]
fn test_type_alias_of_u64_array() {
let dslx = "type MyType = u64[4];";
let sv = simple_convert_for_test(dslx).unwrap();
assert_eq!(sv, "typedef logic [3:0] [63:0] my_type_t;\n");
test_helpers::assert_valid_sv(&sv);
}
#[test]
fn test_convert_leaf_module_enum_def_only() {
let dslx = r#"
enum OpType : u2 { Read = 0, Write = 1 }
"#;
let sv = simple_convert_for_test(dslx).unwrap();
test_helpers::assert_valid_sv(&sv);
assert_eq!(
sv,
r#"typedef enum logic [1:0] {
Read = 2'd0,
Write = 2'd1
} op_type_t;
"#
);
}
#[test]
fn test_convert_leaf_module_enum_def_camel_case() {
let dslx = r#"
enum MyEnum : u2 { MY_FIRST_VALUE = 0, MY_SECOND_VALUE = 1 }
"#;
let sv = simple_convert_for_test(dslx).unwrap();
test_helpers::assert_valid_sv(&sv);
assert_eq!(
sv,
r#"typedef enum logic [1:0] {
MyFirstValue = 2'd0,
MySecondValue = 2'd1
} my_enum_t;
"#
);
}
#[test]
fn test_convert_leaf_module_struct_def_only() {
let dslx = r#"
struct MyStruct {
byte_array: u8[10],
word_data: u16,
}
"#;
let sv = simple_convert_for_test(dslx).unwrap();
assert_eq!(
sv,
r#"typedef struct packed {
logic [9:0] [7:0] byte_array;
logic [15:0] word_data;
} my_struct_t;
"#
);
}
#[test]
fn test_convert_leaf_module_type_alias_to_bits_type_only() {
let dslx = "type MyType = u8;";
let sv = simple_convert_for_test(dslx).unwrap();
assert_eq!(sv, "typedef logic [7:0] my_type_t;\n");
test_helpers::assert_valid_sv(&sv);
}
#[test]
fn test_convert_leaf_module_enum_defs_with_collision() {
let dslx = "enum MyFirstEnum : u1 { A = 0, B = 1 }
enum MySecondEnum: u3 { A = 3, B = 4 }";
let result = simple_convert_for_test(dslx);
let err = result.expect_err("expect collision");
assert!(err.to_string().contains("name collision detected for SV name in generated module namespace: `A` context: DSLX enum `MySecondEnum`"));
}
#[test]
fn test_is_screaming_snake_case() {
assert!(is_screaming_snake_case("FOO_BAR"));
assert!(is_screaming_snake_case("ONEWORD"));
assert!(!is_screaming_snake_case("FooBar"));
assert!(!is_screaming_snake_case("blah"));
}
#[test]
fn test_struct_with_extern_type_ref_member_type_ref_member() {
let imported_dslx = "pub struct MyImportedStruct { a: u8 }";
let importer_dslx = "import imported; struct MyStruct { a: imported::MyImportedStruct }";
let mut import_data = dslx::ImportData::default();
let _imported_typechecked =
dslx::parse_and_typecheck(imported_dslx, "imported.x", "imported", &mut import_data)
.unwrap();
let importer_typechecked =
dslx::parse_and_typecheck(importer_dslx, "importer.x", "importer", &mut import_data)
.unwrap();
let mut builder = SvBridgeBuilder::new();
convert_imported_module(&importer_typechecked, &mut builder).unwrap();
let sv = builder.build();
assert_eq!(
sv,
"typedef struct packed {
imported_sv_pkg::my_imported_struct_t a;
} my_struct_t;
"
);
}
}