1use crate::extensions::{
4 STANDARD_EXTENSIONS, S_CATEGORY_NAMES, S_EXTENSIONS, Z_CATEGORY_NAMES, Z_EXTENSIONS,
5};
6
7#[derive(Debug, Clone)]
9pub struct ExtensionInfo {
10 pub name: String,
11 pub description: String,
12 pub category: String,
13 pub supported: bool,
14}
15
16#[must_use]
18pub fn strip_rv_prefix(base: &str) -> &str {
19 base.strip_prefix("rv64")
20 .or_else(|| base.strip_prefix("rv32"))
21 .unwrap_or(base)
22}
23
24#[must_use]
26pub fn parse_extensions_compact(isa: &str) -> String {
27 let isa = isa.to_lowercase();
28 let mut exts = Vec::new();
29
30 let base = isa.split('_').next().unwrap_or(&isa);
32 let ext_part = strip_rv_prefix(base);
33
34 let has_g = ext_part.contains('g');
36
37 let standard = [
40 ('i', "I", false), ('e', "E", false), ('m', "M", true),
43 ('a', "A", true),
44 ('f', "F", true),
45 ('d', "D", true),
46 ('q', "Q", false),
47 ('c', "C", false),
48 ('b', "B", false),
49 ('v', "V", false),
50 ('h', "H", false),
51 ];
52
53 for (ch, name, implied_by_g) in standard {
54 if ext_part.contains(ch) || (has_g && implied_by_g) {
55 exts.push(name);
56 }
57 }
58
59 if has_g && !exts.contains(&"I") && !exts.contains(&"E") {
61 exts.insert(0, "I");
62 }
63
64 exts.join(" ")
65}
66
67#[must_use]
69pub fn parse_z_extensions(isa: &str) -> String {
70 let isa = isa.to_lowercase();
71 let mut z_exts = Vec::new();
72
73 let base = isa.split('_').next().unwrap_or(&isa);
75 let ext_part = strip_rv_prefix(base);
76 let has_g = ext_part.contains('g');
77
78 if has_g {
80 z_exts.push("zicsr".to_string());
81 z_exts.push("zifencei".to_string());
82 }
83
84 for part in isa.split('_') {
86 if part.starts_with('z') && !z_exts.contains(&part.to_string()) {
87 z_exts.push(part.to_string());
88 }
89 }
90
91 z_exts.join(" ")
92}
93
94#[must_use]
96pub fn parse_s_extensions(isa: &str) -> String {
97 let isa = isa.to_lowercase();
98 let mut s_exts = Vec::new();
99
100 for part in isa.split('_') {
102 if part.starts_with('s') && !s_exts.contains(&part.to_string()) {
103 s_exts.push(part.to_string());
104 }
105 }
106
107 s_exts.join(" ")
108}
109
110#[must_use]
112pub fn parse_extensions_explained(isa: &str) -> Vec<(String, String)> {
113 let isa = isa.to_lowercase();
114 let base = isa.split('_').next().unwrap_or(&isa);
115 let ext_part = strip_rv_prefix(base);
116 let mut exts = Vec::new();
117
118 for &(ch, name, desc) in STANDARD_EXTENSIONS {
119 if ext_part.contains(ch) {
120 exts.push((name.to_string(), desc.to_string()));
121 }
122 }
123
124 exts
125}
126
127#[must_use]
129pub fn parse_z_extensions_explained(isa: &str) -> Vec<(String, String)> {
130 let isa = isa.to_lowercase();
131 let mut z_exts = Vec::new();
132
133 for &(pattern, name, desc, _category) in Z_EXTENSIONS {
134 if isa.contains(pattern) {
135 z_exts.push((name.to_string(), desc.to_string()));
136 }
137 }
138
139 z_exts
140}
141
142#[must_use]
144pub fn parse_s_extensions_explained(isa: &str) -> Vec<(String, String)> {
145 let isa = isa.to_lowercase();
146 let mut s_exts = Vec::new();
147
148 for &(pattern, name, desc, _category) in S_EXTENSIONS {
149 if isa.contains(pattern) {
150 s_exts.push((name.to_string(), desc.to_string()));
151 }
152 }
153
154 s_exts
155}
156
157#[must_use]
159pub fn parse_z_extensions_with_category(isa: &str) -> Vec<ExtensionInfo> {
160 let isa = isa.to_lowercase();
161 let mut z_exts = Vec::new();
162
163 let base = isa.split('_').next().unwrap_or(&isa);
165 let ext_part = strip_rv_prefix(base);
166 let has_g = ext_part.contains('g');
167
168 if has_g {
170 z_exts.push(ExtensionInfo {
171 name: "Zicsr".to_string(),
172 description: "CSR Instructions".to_string(),
173 category: "base".to_string(),
174 supported: true,
175 });
176 z_exts.push(ExtensionInfo {
177 name: "Zifencei".to_string(),
178 description: "Instruction-Fetch Fence".to_string(),
179 category: "base".to_string(),
180 supported: true,
181 });
182 }
183
184 for &(pattern, name, desc, category) in Z_EXTENSIONS {
185 if isa.contains(pattern) {
186 if !z_exts.iter().any(|e| e.name.eq_ignore_ascii_case(name)) {
188 z_exts.push(ExtensionInfo {
189 name: name.to_string(),
190 description: desc.to_string(),
191 category: category.to_string(),
192 supported: true,
193 });
194 }
195 }
196 }
197
198 z_exts
199}
200
201#[must_use]
203pub fn parse_s_extensions_with_category(isa: &str) -> Vec<ExtensionInfo> {
204 let isa = isa.to_lowercase();
205 let mut s_exts = Vec::new();
206
207 for &(pattern, name, desc, category) in S_EXTENSIONS {
208 if isa.contains(pattern) {
209 s_exts.push(ExtensionInfo {
210 name: name.to_string(),
211 description: desc.to_string(),
212 category: category.to_string(),
213 supported: true,
214 });
215 }
216 }
217
218 s_exts
219}
220
221#[must_use]
223pub fn get_z_category_name(category: &str) -> &'static str {
224 Z_CATEGORY_NAMES
225 .iter()
226 .find(|(id, _)| *id == category)
227 .map_or("Other", |(_, name)| *name)
228}
229
230#[must_use]
232pub fn get_s_category_name(category: &str) -> &'static str {
233 S_CATEGORY_NAMES
234 .iter()
235 .find(|(id, _)| *id == category)
236 .map_or("Other", |(_, name)| *name)
237}
238
239#[must_use]
241pub fn group_by_category(extensions: &[ExtensionInfo]) -> Vec<(String, Vec<&ExtensionInfo>)> {
242 use std::collections::BTreeMap;
243 let mut groups: BTreeMap<String, Vec<&ExtensionInfo>> = BTreeMap::new();
244
245 for ext in extensions {
246 groups.entry(ext.category.clone()).or_default().push(ext);
247 }
248
249 groups.into_iter().collect()
250}
251
252#[must_use]
254pub fn get_all_z_extensions_with_status(isa: &str) -> Vec<ExtensionInfo> {
255 let isa = isa.to_lowercase();
256 let base = isa.split('_').next().unwrap_or(&isa);
257 let ext_part = strip_rv_prefix(base);
258 let has_g = ext_part.contains('g');
259
260 Z_EXTENSIONS
261 .iter()
262 .map(|&(pattern, name, desc, category)| {
263 let supported =
264 isa.contains(pattern) || (has_g && (pattern == "zicsr" || pattern == "zifencei"));
265 ExtensionInfo {
266 name: name.to_string(),
267 description: desc.to_string(),
268 category: category.to_string(),
269 supported,
270 }
271 })
272 .collect()
273}
274
275#[must_use]
277pub fn get_all_s_extensions_with_status(isa: &str) -> Vec<ExtensionInfo> {
278 let isa = isa.to_lowercase();
279
280 S_EXTENSIONS
281 .iter()
282 .map(|&(pattern, name, desc, category)| {
283 let supported = isa.contains(pattern);
284 ExtensionInfo {
285 name: name.to_string(),
286 description: desc.to_string(),
287 category: category.to_string(),
288 supported,
289 }
290 })
291 .collect()
292}
293
294#[must_use]
296pub fn get_all_standard_extensions_with_status(isa: &str) -> Vec<(String, String, bool)> {
297 let isa = isa.to_lowercase();
298 let base = isa.split('_').next().unwrap_or(&isa);
299 let ext_part = strip_rv_prefix(base);
300 let has_g = ext_part.contains('g');
301
302 STANDARD_EXTENSIONS
303 .iter()
304 .map(|&(char, name, desc)| {
305 let supported =
306 ext_part.contains(char) || (has_g && matches!(char, 'i' | 'm' | 'a' | 'f' | 'd'));
307 (name.to_string(), desc.to_string(), supported)
308 })
309 .collect()
310}
311
312#[must_use]
315pub fn parse_vector_from_isa(isa: &str) -> Option<String> {
316 let isa = isa.to_lowercase();
317 let base = isa.split('_').next().unwrap_or(&isa);
318 let ext_part = strip_rv_prefix(base);
319
320 if !ext_part.contains('v') && !isa.contains("zve") {
322 return None;
323 }
324
325 let mut details = vec!["Enabled".to_string()];
326
327 if isa.contains("zvl65536b") {
330 details.push("VLEN>=65536".to_string());
331 } else if isa.contains("zvl32768b") {
332 details.push("VLEN>=32768".to_string());
333 } else if isa.contains("zvl16384b") {
334 details.push("VLEN>=16384".to_string());
335 } else if isa.contains("zvl8192b") {
336 details.push("VLEN>=8192".to_string());
337 } else if isa.contains("zvl4096b") {
338 details.push("VLEN>=4096".to_string());
339 } else if isa.contains("zvl2048b") {
340 details.push("VLEN>=2048".to_string());
341 } else if isa.contains("zvl1024b") {
342 details.push("VLEN>=1024".to_string());
343 } else if isa.contains("zvl512b") {
344 details.push("VLEN>=512".to_string());
345 } else if isa.contains("zvl256b") {
346 details.push("VLEN>=256".to_string());
347 } else if isa.contains("zvl128b") {
348 details.push("VLEN>=128".to_string());
349 } else if isa.contains("zvl64b") {
350 details.push("VLEN>=64".to_string());
351 } else if isa.contains("zvl32b") {
352 details.push("VLEN>=32".to_string());
353 }
354 Some(details.join(", "))
357}
358
359#[cfg(test)]
360mod tests {
361 use super::*;
362
363 const ISA_VISIONFIVE2: &str = "rv64imafdc_zicntr_zicsr_zifencei_zihpm_zba_zbb";
365 const ISA_SPACEMIT_K1: &str = "rv64imafdcv_zicbom_zicboz_zicntr_zicsr_zifencei_zihintpause_zihpm_zba_zbb_zbc_zbs_zkt_zvkt_zvl128b_zvl256b_zvl32b_zvl64b";
366 const ISA_MINIMAL: &str = "rv64imac";
367 const ISA_RV32: &str = "rv32imc";
368
369 #[test]
372 fn test_visionfive2() {
373 assert_eq!(parse_extensions_compact(ISA_VISIONFIVE2), "I M A F D C");
374 }
375
376 #[test]
377 fn test_spacemit() {
378 assert_eq!(parse_extensions_compact(ISA_SPACEMIT_K1), "I M A F D C V");
379 }
380
381 #[test]
382 fn test_minimal() {
383 assert_eq!(parse_extensions_compact(ISA_MINIMAL), "I M A C");
384 }
385
386 #[test]
387 fn test_rv32() {
388 assert_eq!(parse_extensions_compact(ISA_RV32), "I M C");
389 }
390
391 #[test]
392 fn test_unknown() {
393 assert_eq!(parse_extensions_compact("unknown"), "");
394 }
395
396 #[test]
397 fn test_case_insensitive() {
398 assert_eq!(
399 parse_extensions_compact("RV64IMAFDC"),
400 parse_extensions_compact("rv64imafdc")
401 );
402 }
403
404 #[test]
405 fn test_empty() {
406 assert_eq!(parse_extensions_compact(""), "");
407 }
408
409 #[test]
412 fn spec_g_expansion() {
413 assert_eq!(parse_extensions_compact("rv64gc"), "I M A F D C");
414 }
415
416 #[test]
417 fn spec_g_expansion_uppercase() {
418 assert_eq!(parse_extensions_compact("RV64GC"), "I M A F D C");
419 }
420
421 #[test]
422 fn spec_e_extension() {
423 assert_eq!(parse_extensions_compact("rv32e"), "E");
424 }
425
426 #[test]
427 fn spec_e_with_c() {
428 assert_eq!(parse_extensions_compact("rv32ec"), "E C");
429 }
430
431 #[test]
432 fn spec_with_vector() {
433 assert_eq!(parse_extensions_compact("rv64imafdcv"), "I M A F D C V");
434 }
435
436 #[test]
437 fn spec_rv64_prefix_not_vector() {
438 let result = parse_extensions_compact("rv64imafdc");
439 assert!(!result.contains('V'));
440 }
441
442 #[test]
443 fn spec_z_extensions_ignored() {
444 assert_eq!(
445 parse_extensions_compact("rv64imafdc_zba_zbb"),
446 "I M A F D C"
447 );
448 }
449
450 #[test]
451 fn spec_rv64_only() {
452 assert_eq!(parse_extensions_compact("rv64"), "");
453 }
454
455 #[test]
458 fn test_z_extensions_visionfive2() {
459 let result = parse_z_extensions(ISA_VISIONFIVE2);
460 assert!(result.contains("zicntr"));
461 assert!(result.contains("zicsr"));
462 assert!(result.contains("zifencei"));
463 assert!(result.contains("zba"));
464 assert!(result.contains("zbb"));
465 }
466
467 #[test]
468 fn test_z_extensions_spacemit() {
469 let result = parse_z_extensions(ISA_SPACEMIT_K1);
470 assert!(result.contains("zicbom"));
471 assert!(result.contains("zicboz"));
472 assert!(result.contains("zbc"));
473 assert!(result.contains("zbs"));
474 assert!(result.contains("zvl256b"));
475 }
476
477 #[test]
478 fn test_z_extensions_minimal() {
479 assert!(parse_z_extensions(ISA_MINIMAL).is_empty());
480 }
481
482 #[test]
483 fn spec_z_extensions_basic() {
484 assert_eq!(parse_z_extensions("rv64i_zicsr_zifencei"), "zicsr zifencei");
485 }
486
487 #[test]
488 fn spec_z_extensions_order() {
489 assert_eq!(parse_z_extensions("rv64i_zba_zbb_zbc"), "zba zbb zbc");
490 }
491
492 #[test]
493 fn spec_z_extensions_none() {
494 assert_eq!(parse_z_extensions("rv64imafdc"), "");
495 }
496
497 #[test]
498 fn spec_z_extensions_g_implies() {
499 assert_eq!(parse_z_extensions("rv64gc"), "zicsr zifencei");
500 }
501
502 #[test]
503 fn spec_z_extensions_case() {
504 assert_eq!(parse_z_extensions("rv64i_Zicsr"), "zicsr");
505 }
506
507 #[test]
510 fn spec_s_extensions() {
511 let result = parse_s_extensions("rv64i_sstc");
512 assert!(result.contains("sstc"));
513 }
514
515 #[test]
518 fn test_explained_visionfive2() {
519 let result = parse_extensions_explained(ISA_VISIONFIVE2);
520 assert_eq!(result.len(), 6); assert!(result.iter().any(|(n, _)| n == "I"));
522 assert!(result.iter().any(|(n, _)| n == "M"));
523 assert!(result.iter().any(|(n, _)| n == "F"));
524 assert!(result.iter().any(|(n, _)| n == "D"));
525 assert!(result.iter().any(|(n, _)| n == "C"));
526 }
527
528 #[test]
529 fn test_z_explained_spacemit() {
530 let result = parse_z_extensions_explained(ISA_SPACEMIT_K1);
531 assert!(result
532 .iter()
533 .any(|(n, d)| n == "Zba" && d == "Address Generation"));
534 assert!(result
535 .iter()
536 .any(|(n, d)| n == "Zbb" && d == "Basic Bit Manipulation"));
537 assert!(result
538 .iter()
539 .any(|(n, d)| n == "Zbc" && d == "Carry-less Multiply"));
540 }
541
542 #[test]
545 fn test_vector_no_vector() {
546 assert!(parse_vector_from_isa(ISA_VISIONFIVE2).is_none());
547 }
548
549 #[test]
550 fn test_vector_with_v() {
551 let result = parse_vector_from_isa(ISA_SPACEMIT_K1);
552 assert!(result.is_some());
553 let detail = result.unwrap();
554 assert!(detail.contains("Enabled"));
555 assert!(detail.contains("VLEN>=256"));
556 }
557
558 #[test]
559 fn test_vector_zve_only() {
560 let result = parse_vector_from_isa("rv64imac_zve32x");
561 assert!(result.is_some());
562 assert!(result.unwrap().contains("Enabled"));
563 }
564
565 #[test]
566 fn spec_vector_with_v() {
567 let result = parse_vector_from_isa("rv64imafdcv");
568 assert!(result.is_some());
569 assert!(result.unwrap().contains("Enabled"));
570 }
571
572 #[test]
573 fn spec_vector_none() {
574 assert!(parse_vector_from_isa("rv64imafdc").is_none());
575 }
576
577 #[test]
578 fn spec_vector_vlen_256() {
579 let result = parse_vector_from_isa("rv64imafdcv_zvl256b");
580 assert!(result.is_some());
581 assert!(result.unwrap().contains("VLEN>=256"));
582 }
583
584 #[test]
585 fn spec_vector_vlen_largest() {
586 let result = parse_vector_from_isa("rv64imafdcv_zvl128b_zvl256b");
587 assert!(result.is_some());
588 assert!(result.unwrap().contains("VLEN>=256"));
589 }
590
591 #[test]
592 fn spec_vector_no_default_vlen() {
593 let result = parse_vector_from_isa("rv64imafdcv");
594 assert!(result.is_some());
595 let detail = result.unwrap();
596 assert!(detail.contains("Enabled"));
597 assert!(!detail.contains("VLEN"));
598 }
599}