From c2e9b0203f1817f8b53194f0e12f68beb954723d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20Teichgr=C3=A4ber?= Date: Sun, 25 May 2025 00:39:02 +0200 Subject: [PATCH 01/11] tools/gen-device-svd: orderPeripherals: prevent skipping base peripherals derived by name Recent SVDs from stm32-rs, like the one for stm32u595, define derivedFrom attributes that do not refer to a peripheral group name, but to a peripheral name. For instance, the peripheral I2C5 may be derived from "I2C1", not from "I2C". The previous algorithm records, in case the group name is non-empty, only the group name in the knownBasePeripherals map, not the name of the peripheral itself. So in case of the base peripheral I2C1 with group name I2C: although the peripheral gets added to the sorted list, it would be added to knownBasePeripherals with the group name "I2C" as key, not with "I2C1". A following peripheral, I2C5, derived from I2C1, with an empty group name, would be recorded as known with key "I2C5", but omitted from the sorted list, because "I2C1" is not recognized as known. The following peripheral SEC_I2C5, derived from I2C5, with empty group name, would be added to both the knownPeripherals map and the sorted list. So if, later, the sorted list is examined, it would find SEC_I2C5 earlier than its base peripheral I2C5, which would be missing from "peripheralDict", resulting in a nil pointer access. This patch makes sure that, to stay with the example, that "I2C1" is recorded as known too (not only the group name "I2C"), so that "I2C5" won't be skipped anymore, preventing the program from crashing. --- tools/gen-device-svd/gen-device-svd.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tools/gen-device-svd/gen-device-svd.go b/tools/gen-device-svd/gen-device-svd.go index 1a3d539621..df431ec269 100755 --- a/tools/gen-device-svd/gen-device-svd.go +++ b/tools/gen-device-svd/gen-device-svd.go @@ -493,10 +493,10 @@ func orderPeripherals(input []SVDPeripheral) []*SVDPeripheral { for i := range input { p := &input[i] groupName := p.GroupName - if groupName == "" { - groupName = p.Name + if groupName != "" { + knownBasePeripherals[groupName] = struct{}{} } - knownBasePeripherals[groupName] = struct{}{} + knownBasePeripherals[p.Name] = struct{}{} if p.DerivedFrom != "" { if _, ok := knownBasePeripherals[p.DerivedFrom]; !ok { missingBasePeripherals = append(missingBasePeripherals, p) From 19b972309136e7cca2caeb2a42410ace4da60c06 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20Teichgr=C3=A4ber?= Date: Sun, 25 May 2025 01:01:21 +0200 Subject: [PATCH 02/11] tools/gen-device-svd: orderPeripherals: ensure ordered content of missingBasePeripherals After the first run, missingBasePeripherals may contain peripherals with dependencies that are not guaranteed to be in proper order. This change implements additional loop runs that try to reduce the size of the missingBasePeripherals as far as possible. [With recent SVDs from stm32-rs this change will not produce different results, though (since these source files contain already properly ordered peripherals).] --- tools/gen-device-svd/gen-device-svd.go | 26 +++++++++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/tools/gen-device-svd/gen-device-svd.go b/tools/gen-device-svd/gen-device-svd.go index df431ec269..09c776f5d5 100755 --- a/tools/gen-device-svd/gen-device-svd.go +++ b/tools/gen-device-svd/gen-device-svd.go @@ -490,8 +490,8 @@ func orderPeripherals(input []SVDPeripheral) []*SVDPeripheral { var sortedPeripherals []*SVDPeripheral var missingBasePeripherals []*SVDPeripheral knownBasePeripherals := map[string]struct{}{} - for i := range input { - p := &input[i] + + tryProcess := func(p *SVDPeripheral) { groupName := p.GroupName if groupName != "" { knownBasePeripherals[groupName] = struct{}{} @@ -500,11 +500,31 @@ func orderPeripherals(input []SVDPeripheral) []*SVDPeripheral { if p.DerivedFrom != "" { if _, ok := knownBasePeripherals[p.DerivedFrom]; !ok { missingBasePeripherals = append(missingBasePeripherals, p) - continue + return } } sortedPeripherals = append(sortedPeripherals, p) } + for i := range input { + tryProcess(&input[i]) + } + + // missingBasePeripherals may still contain unordered entries; + // repeat the process until missingBasePeripheral does not change anymore. + prevNumPending := 0 + for { + pending := missingBasePeripherals + if len(pending) == prevNumPending { + break + } + // reuse the same slice as input and for keeping track of + // missing base periphal + missingBasePeripherals = missingBasePeripherals[:0] + for _, p := range pending { + tryProcess(p) + } + prevNumPending = len(pending) + } // Let's hope all base peripherals are now included. sortedPeripherals = append(sortedPeripherals, missingBasePeripherals...) From f0ec19867194219a43880c0e5c52cfe2ce3b6946 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20Teichgr=C3=A4ber?= Date: Sun, 25 May 2025 01:33:12 +0200 Subject: [PATCH 03/11] tools/gen-device-svd: Register: move dim array decoding to utility type dimArray This allows encoding of dim increment and array indices to be re-used by other elements supporting dim arrays. This change just restructures parts of register specific code, it does not change the output of the program. --- tools/gen-device-svd/gen-device-svd.go | 112 +++++++++++++++---------- 1 file changed, 69 insertions(+), 43 deletions(-) diff --git a/tools/gen-device-svd/gen-device-svd.go b/tools/gen-device-svd/gen-device-svd.go index 09c776f5d5..a1005cb0a4 100755 --- a/tools/gen-device-svd/gen-device-svd.go +++ b/tools/gen-device-svd/gen-device-svd.go @@ -759,38 +759,57 @@ func (r *Register) address() uint64 { } func (r *Register) dim() int { - if r.element.Dim == nil { + return decodeDim(r.element.Dim) +} + +func decodeDim(s *string) int { + if s == nil { return -1 // no dim elements } - dim, err := strconv.ParseInt(*r.element.Dim, 0, 32) + dim, err := strconv.ParseInt(*s, 0, 32) if err != nil { panic(err) } return int(dim) } -func (r *Register) dimIndex() []string { +type dimArray struct { + dim int + idx []string + incr uint32 +} + +func decodeDimArray(dimSpec, dimIndex *string, dimIncr, elType, elName string) *dimArray { + dim := decodeDim(dimSpec) + if dim <= 0 { + return nil + } + a := new(dimArray) + a.dim = dim + defer func() { if err := recover(); err != nil { - fmt.Println("register", r.name()) + fmt.Println(elType, elName) panic(err) } }() - dim := r.dim() - if r.element.DimIndex == nil { - if dim <= 0 { - return nil - } + incr, err := strconv.ParseUint(dimIncr, 0, 32) + if err != nil { + panic(err) + } + a.incr = uint32(incr) + if dimIndex == nil { idx := make([]string, dim) for i := range idx { idx[i] = strconv.FormatInt(int64(i), 10) } - return idx + a.idx = idx + return a } - t := strings.Split(*r.element.DimIndex, "-") + t := strings.Split(*dimIndex, "-") if len(t) == 2 { // renesas uses hex letters e.g. A-B if strings.Contains("ABCDEFabcdef", t[0]) { @@ -817,17 +836,29 @@ func (r *Register) dimIndex() []string { for i := x; i <= y; i++ { idx[i-x] = strconv.FormatInt(i, 10) } - return idx + a.idx = idx + return a } else if len(t) > 2 { panic("invalid dimIndex") } - s := strings.Split(*r.element.DimIndex, ",") + s := strings.Split(*dimIndex, ",") if len(s) != dim { panic("invalid dimIndex") } + a.idx = s + return a +} + +func (da *dimArray) replace(s string, i int) string { + if da == nil { + return s + } + if i >= len(da.idx) { + return s + } + return strings.ReplaceAll(s, "%s", da.idx[i]) - return s } func (r *Register) size() int { @@ -843,37 +874,32 @@ func (r *Register) size() int { func parseRegister(groupName string, regEl *SVDRegister, baseAddress uint64, bitfieldPrefix string) []*PeripheralField { reg := NewRegister(regEl, baseAddress) - - if reg.dim() != -1 { - dimIncrement, err := strconv.ParseUint(regEl.DimIncrement, 0, 32) - if err != nil { - panic(err) + name := reg.name() + da := decodeDimArray(regEl.Dim, regEl.DimIndex, regEl.DimIncrement, "register", name) + if da != nil && strings.Contains(name, "%s") { + // a "spaced array" of registers, special processing required + // we need to generate a separate register for each "element" + var results []*PeripheralField + shortName := strings.ToUpper(strings.ReplaceAll(strings.ReplaceAll(name, "_%s", ""), "%s", "")) + for i := range da.idx { + regAddress := reg.address() + (uint64(i) * uint64(da.incr)) + results = append(results, &PeripheralField{ + Name: strings.ToUpper(da.replace(name, i)), + Address: regAddress, + Description: reg.description(), + Array: -1, + ElementSize: reg.size(), + ShortName: shortName, + }) } - if strings.Contains(reg.name(), "%s") { - // a "spaced array" of registers, special processing required - // we need to generate a separate register for each "element" - var results []*PeripheralField - shortName := strings.ToUpper(strings.ReplaceAll(strings.ReplaceAll(reg.name(), "_%s", ""), "%s", "")) - for i, j := range reg.dimIndex() { - regAddress := reg.address() + (uint64(i) * dimIncrement) - results = append(results, &PeripheralField{ - Name: strings.ToUpper(strings.ReplaceAll(reg.name(), "%s", j)), - Address: regAddress, - Description: reg.description(), - Array: -1, - ElementSize: reg.size(), - ShortName: shortName, - }) - } - // set first result bitfield - results[0].Constants, results[0].Bitfields = parseBitfields(groupName, shortName, regEl.Fields, bitfieldPrefix) - results[0].HasBitfields = len(results[0].Bitfields) > 0 - for i := 1; i < len(results); i++ { - results[i].Bitfields = results[0].Bitfields - results[i].HasBitfields = results[0].HasBitfields - } - return results + // set first result bitfield + results[0].Constants, results[0].Bitfields = parseBitfields(groupName, shortName, regEl.Fields, bitfieldPrefix) + results[0].HasBitfields = len(results[0].Bitfields) > 0 + for i := 1; i < len(results); i++ { + results[i].Bitfields = results[0].Bitfields + results[i].HasBitfields = results[0].HasBitfields } + return results } regName := reg.name() if !unicode.IsUpper(rune(regName[0])) && !unicode.IsDigit(rune(regName[0])) { From bd1762fb5fbd402d50b1f11f8278a9c325fd2c33 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20Teichgr=C3=A4ber?= Date: Sun, 25 May 2025 02:04:58 +0200 Subject: [PATCH 04/11] tools/gen-device-svd: parseBitfields: support field dim arrays Patched SVD files from stm32-rs recently contain many fields with dim array parameters and names containing %s (like "CC%sIF"). This change adjusts parseBitfields so that these field elements get resolved. --- tools/gen-device-svd/gen-device-svd.go | 269 ++++++++++++++----------- 1 file changed, 154 insertions(+), 115 deletions(-) diff --git a/tools/gen-device-svd/gen-device-svd.go b/tools/gen-device-svd/gen-device-svd.go index a1005cb0a4..cfba46cbc8 100755 --- a/tools/gen-device-svd/gen-device-svd.go +++ b/tools/gen-device-svd/gen-device-svd.go @@ -18,6 +18,7 @@ import ( ) var validName = regexp.MustCompile("^[a-zA-Z0-9_]+$") +var validDimableName = regexp.MustCompile(`^((%s)|(%s)[_A-Za-z]{1}[_A-Za-z0-9]*)|([_A-Za-z]{1}[_A-Za-z0-9]*(\[%s\])?)|([_A-Za-z]{1}[_A-Za-z0-9]*(%s)?[_A-Za-z0-9]*)$`) var enumBitSpecifier = regexp.MustCompile("^#x*[01]+[01x]*$") type SVDFile struct { @@ -62,6 +63,9 @@ type SVDRegister struct { type SVDField struct { Name string `xml:"name"` Description string `xml:"description"` + Dim *string `xml:"dim"` + DimIndex *string `xml:"dimIndex"` + DimIncrement string `xml:"dimIncrement"` Lsb *uint32 `xml:"lsb"` Msb *uint32 `xml:"msb"` BitOffset *uint32 `xml:"bitOffset"` @@ -173,23 +177,36 @@ func splitLine(s string) []string { // Replace characters that are not allowed in a symbol name with a '_'. This is // useful to be able to process SVD files with errors. func cleanName(text string) string { - if !validName.MatchString(text) { - result := make([]rune, 0, len(text)) - for _, c := range text { + return cleanIdentifier(text, validName) +} + +func cleanDimableName(text string) string { + return cleanIdentifier(text, validDimableName) +} + +func cleanIdentifier(text string, valid *regexp.Regexp) string { + text = cleanString(text, valid) + if len(text) != 0 && (text[0] >= '0' && text[0] <= '9') { + // Identifiers may not start with a number. + // Add an underscore instead. + text = "_" + text + } + return text +} + +func cleanString(s string, valid *regexp.Regexp) string { + if !valid.MatchString(s) { + result := make([]rune, 0, len(s)) + for _, c := range s { if validName.MatchString(string(c)) { result = append(result, c) } else { result = append(result, '_') } } - text = string(result) + s = string(result) } - if len(text) != 0 && (text[0] >= '0' && text[0] <= '9') { - // Identifiers may not start with a number. - // Add an underscore instead. - text = "_" + text - } - return text + return s } func processSubCluster(p *Peripheral, cluster *SVDCluster, clusterOffset uint64, clusterName string, peripheralDict map[string]*Peripheral) []*Peripheral { @@ -569,9 +586,9 @@ func parseBitfields(groupName, regName string, fieldEls []*SVDField, bitfieldPre // Some bitfields (like the STM32H7x7) contain invalid bitfield // names like "CNT[31]". Replace invalid characters with "_" when // needed. - fieldName := cleanName(fieldEl.Name) - if !unicode.IsUpper(rune(fieldName[0])) && !unicode.IsDigit(rune(fieldName[0])) { - fieldName = strings.ToUpper(fieldName) + fieldNameTpl := cleanDimableName(fieldEl.Name) + if !unicode.IsUpper(rune(fieldNameTpl[0])) && !unicode.IsDigit(rune(fieldNameTpl[0])) { + fieldNameTpl = strings.ReplaceAll(strings.ToUpper(fieldNameTpl), "%S", "%s") } // Find the lsb/msb that is encoded in various ways. @@ -601,127 +618,138 @@ func parseBitfields(groupName, regName string, fieldEls []*SVDField, bitfieldPre msb = uint32(m) } else { // this is an error. what to do? - fmt.Fprintln(os.Stderr, "unable to find lsb/msb in field:", fieldName) + fmt.Fprintln(os.Stderr, "unable to find lsb/msb in field:", fieldNameTpl) continue } - // The enumerated values can be the same as another field, so to avoid - // duplication SVD files can simply refer to another set of enumerated - // values in the same register. - // See: https://www.keil.com/pack/doc/CMSIS/SVD/html/elem_registers.html#elem_enumeratedValues - enumeratedValues := fieldEl.EnumeratedValues - if enumeratedValues.DerivedFrom != "" { - parts := strings.Split(enumeratedValues.DerivedFrom, ".") - if len(parts) == 1 { - found := false - for _, otherFieldEl := range fieldEls { - if otherFieldEl.EnumeratedValues.Name == parts[0] { - found = true - enumeratedValues = otherFieldEl.EnumeratedValues + da := decodeDimArray(fieldEl.Dim, fieldEl.DimIndex, fieldEl.DimIncrement, "field", fieldNameTpl) + da.rangeElems(func(ia int, _ uint32) bool { + if da != nil { + lsb += da.incr + msb += da.incr + } + fieldName := da.replace(fieldNameTpl, ia) + + // The enumerated values can be the same as another field, so to avoid + // duplication SVD files can simply refer to another set of enumerated + // values in the same register. + // See: https://www.keil.com/pack/doc/CMSIS/SVD/html/elem_registers.html#elem_enumeratedValues + enumeratedValues := fieldEl.EnumeratedValues + if enumeratedValues.DerivedFrom != "" { + parts := strings.Split(enumeratedValues.DerivedFrom, ".") + if len(parts) == 1 { + found := false + for _, otherFieldEl := range fieldEls { + if otherFieldEl.EnumeratedValues.Name == parts[0] { + found = true + enumeratedValues = otherFieldEl.EnumeratedValues + } } + if !found { + fmt.Fprintf(os.Stderr, "Warning: could not find enumeratedValue.derivedFrom of %s for register field %s\n", enumeratedValues.DerivedFrom, fieldName) + } + } else { + // The derivedFrom attribute may also point to enumerated values + // in other registers and even peripherals, but this feature + // isn't often used in SVD files. + fmt.Fprintf(os.Stderr, "TODO: enumeratedValue.derivedFrom to a different register: %s\n", enumeratedValues.DerivedFrom) } - if !found { - fmt.Fprintf(os.Stderr, "Warning: could not find enumeratedValue.derivedFrom of %s for register field %s\n", enumeratedValues.DerivedFrom, fieldName) - } - } else { - // The derivedFrom attribute may also point to enumerated values - // in other registers and even peripherals, but this feature - // isn't often used in SVD files. - fmt.Fprintf(os.Stderr, "TODO: enumeratedValue.derivedFrom to a different register: %s\n", enumeratedValues.DerivedFrom) } - } - bitfields = append(bitfields, Bitfield{ - Name: fieldName, - Offset: lsb, - Mask: (0xffffffff >> (31 - (msb - lsb))) << lsb, - }) - fields = append(fields, Constant{ - Name: fmt.Sprintf("%s_%s%s_%s_Pos", groupName, bitfieldPrefix, regName, fieldName), - Description: fmt.Sprintf("Position of %s field.", fieldName), - Value: uint64(lsb), - }) - fields = append(fields, Constant{ - Name: fmt.Sprintf("%s_%s%s_%s_Msk", groupName, bitfieldPrefix, regName, fieldName), - Description: fmt.Sprintf("Bit mask of %s field.", fieldName), - Value: (0xffffffffffffffff >> (63 - (msb - lsb))) << lsb, - }) - if lsb == msb { // single bit + bitfields = append(bitfields, Bitfield{ + Name: fieldName, + Offset: lsb, + Mask: (0xffffffff >> (31 - (msb - lsb))) << lsb, + }) fields = append(fields, Constant{ - Name: fmt.Sprintf("%s_%s%s_%s", groupName, bitfieldPrefix, regName, fieldName), - Description: fmt.Sprintf("Bit %s.", fieldName), - Value: 1 << lsb, + Name: fmt.Sprintf("%s_%s%s_%s_Pos", groupName, bitfieldPrefix, regName, fieldName), + Description: fmt.Sprintf("Position of %s field.", fieldName), + Value: uint64(lsb), }) - } - for _, enumEl := range enumeratedValues.EnumeratedValue { - enumName := enumEl.Name - // Renesas has enum without actual values that we have to skip - if enumEl.Value == "" { - continue + fields = append(fields, Constant{ + Name: fmt.Sprintf("%s_%s%s_%s_Msk", groupName, bitfieldPrefix, regName, fieldName), + Description: fmt.Sprintf("Bit mask of %s field.", fieldName), + Value: (0xffffffffffffffff >> (63 - (msb - lsb))) << lsb, + }) + if lsb == msb { // single bit + fields = append(fields, Constant{ + Name: fmt.Sprintf("%s_%s%s_%s", groupName, bitfieldPrefix, regName, fieldName), + Description: fmt.Sprintf("Bit %s.", fieldName), + Value: 1 << lsb, + }) } + for _, enumEl := range enumeratedValues.EnumeratedValue { + enumName := enumEl.Name + // Renesas has enum without actual values that we have to skip + if enumEl.Value == "" { + continue + } - if strings.EqualFold(enumName, "reserved") || !validName.MatchString(enumName) { - continue - } - if !unicode.IsUpper(rune(enumName[0])) && !unicode.IsDigit(rune(enumName[0])) { - enumName = strings.ToUpper(enumName) - } - enumDescription := formatText(enumEl.Description) - var enumValue uint64 - var err error - if strings.HasPrefix(enumEl.Value, "0b") { - val := strings.TrimPrefix(enumEl.Value, "0b") - enumValue, err = strconv.ParseUint(val, 2, 64) - } else { - enumValue, err = strconv.ParseUint(enumEl.Value, 0, 64) - } - if err != nil { - if enumBitSpecifier.MatchString(enumEl.Value) { - // NXP and Renesas SVDs use the form #xx1x, #x0xx, etc for values - enumValue, err = strconv.ParseUint(strings.ReplaceAll(enumEl.Value[1:], "x", "0"), 2, 64) - if err != nil { - panic(err) - } + if strings.EqualFold(enumName, "reserved") || !validName.MatchString(enumName) { + continue + } + if !unicode.IsUpper(rune(enumName[0])) && !unicode.IsDigit(rune(enumName[0])) { + enumName = strings.ToUpper(enumName) + } + enumDescription := formatText(enumEl.Description) + var enumValue uint64 + var err error + if strings.HasPrefix(enumEl.Value, "0b") { + val := strings.TrimPrefix(enumEl.Value, "0b") + enumValue, err = strconv.ParseUint(val, 2, 64) } else { - panic(err) + enumValue, err = strconv.ParseUint(enumEl.Value, 0, 64) } - } - enumName = fmt.Sprintf("%s_%s%s_%s_%s", groupName, bitfieldPrefix, regName, fieldName, enumName) - - // Avoid duplicate values. Duplicate names with the same value are - // allowed, but the same name with a different value is not. Instead - // of trying to work around those cases, remove the value entirely - // as there is probably not one correct answer in such a case. - // For example, SVD files from NXP have enums limited to 20 - // characters, leading to lots of duplicates when these enum names - // are long. Nothing here can really fix those cases. - previousEnumValue, seenBefore := enumSeen[enumName] - if seenBefore { - if previousEnumValue < 0 { - // There was a mismatch before, ignore all equally named fields. - continue + if err != nil { + if enumBitSpecifier.MatchString(enumEl.Value) { + // NXP and Renesas SVDs use the form #xx1x, #x0xx, etc for values + enumValue, err = strconv.ParseUint(strings.ReplaceAll(enumEl.Value[1:], "x", "0"), 2, 64) + if err != nil { + panic(err) + } + } else { + panic(err) + } } - if int64(enumValue) != previousEnumValue { - // There is a mismatch. Mark it as such, and remove the - // existing enum bitfield value. - enumSeen[enumName] = -1 - for i, field := range fields { - if field.Name == enumName { - fields = append(fields[:i], fields[i+1:]...) - break + enumName = fmt.Sprintf("%s_%s%s_%s_%s", groupName, bitfieldPrefix, regName, fieldName, enumName) + + // Avoid duplicate values. Duplicate names with the same value are + // allowed, but the same name with a different value is not. Instead + // of trying to work around those cases, remove the value entirely + // as there is probably not one correct answer in such a case. + // For example, SVD files from NXP have enums limited to 20 + // characters, leading to lots of duplicates when these enum names + // are long. Nothing here can really fix those cases. + previousEnumValue, seenBefore := enumSeen[enumName] + if seenBefore { + if previousEnumValue < 0 { + // There was a mismatch before, ignore all equally named fields. + continue + } + if int64(enumValue) != previousEnumValue { + // There is a mismatch. Mark it as such, and remove the + // existing enum bitfield value. + enumSeen[enumName] = -1 + for i, field := range fields { + if field.Name == enumName { + fields = append(fields[:i], fields[i+1:]...) + break + } } } + continue } - continue + enumSeen[enumName] = int64(enumValue) + + fields = append(fields, Constant{ + Name: enumName, + Description: enumDescription, + Value: enumValue, + }) } - enumSeen[enumName] = int64(enumValue) - fields = append(fields, Constant{ - Name: enumName, - Description: enumDescription, - Value: enumValue, - }) - } + return true + }) } return fields, bitfields } @@ -858,7 +886,18 @@ func (da *dimArray) replace(s string, i int) string { return s } return strings.ReplaceAll(s, "%s", da.idx[i]) +} +func (da *dimArray) rangeElems(yield func(i int, incr uint32) bool) { + if da == nil { + yield(0, 0) + return + } + for i := range da.dim { + if !yield(i, uint32(i)*da.incr) { + return + } + } } func (r *Register) size() int { From 68e6a0216284232d716becf74a9e178f9daea53a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20Teichgr=C3=A4ber?= Date: Sun, 25 May 2025 02:29:48 +0200 Subject: [PATCH 05/11] tools/gen-device-svd: SVDField: allow multiple enumeratedValues In recent patched SVD files from stm32-rs there may be two enumeratedValues elements per SVDField, not just one. The SVD specification allows up to two entries (they may be used to define different enums for read and write access). This change extends SVDField and parseBitfields so that two enumeratedValues are processed like a single one. --- tools/gen-device-svd/gen-device-svd.go | 202 +++++++++++++------------ 1 file changed, 105 insertions(+), 97 deletions(-) diff --git a/tools/gen-device-svd/gen-device-svd.go b/tools/gen-device-svd/gen-device-svd.go index cfba46cbc8..30117ce5df 100755 --- a/tools/gen-device-svd/gen-device-svd.go +++ b/tools/gen-device-svd/gen-device-svd.go @@ -61,25 +61,17 @@ type SVDRegister struct { } type SVDField struct { - Name string `xml:"name"` - Description string `xml:"description"` - Dim *string `xml:"dim"` - DimIndex *string `xml:"dimIndex"` - DimIncrement string `xml:"dimIncrement"` - Lsb *uint32 `xml:"lsb"` - Msb *uint32 `xml:"msb"` - BitOffset *uint32 `xml:"bitOffset"` - BitWidth *uint32 `xml:"bitWidth"` - BitRange *string `xml:"bitRange"` - EnumeratedValues struct { - DerivedFrom string `xml:"derivedFrom,attr"` - Name string `xml:"name"` - EnumeratedValue []struct { - Name string `xml:"name"` - Description string `xml:"description"` - Value string `xml:"value"` - } `xml:"enumeratedValue"` - } `xml:"enumeratedValues"` + Name string `xml:"name"` + Description string `xml:"description"` + Dim *string `xml:"dim"` + DimIndex *string `xml:"dimIndex"` + DimIncrement string `xml:"dimIncrement"` + Lsb *uint32 `xml:"lsb"` + Msb *uint32 `xml:"msb"` + BitOffset *uint32 `xml:"bitOffset"` + BitWidth *uint32 `xml:"bitWidth"` + BitRange *string `xml:"bitRange"` + EnumeratedValues []SVDEnumeration `xml:"enumeratedValues"` } type SVDCluster struct { @@ -93,6 +85,16 @@ type SVDCluster struct { AddressOffset string `xml:"addressOffset"` } +type SVDEnumeration struct { + DerivedFrom string `xml:"derivedFrom,attr"` + Name string `xml:"name"` + EnumeratedValue []struct { + Name string `xml:"name"` + Description string `xml:"description"` + Value string `xml:"value"` + } `xml:"enumeratedValue"` +} + type Device struct { Metadata *Metadata Interrupts []*Interrupt @@ -634,25 +636,30 @@ func parseBitfields(groupName, regName string, fieldEls []*SVDField, bitfieldPre // duplication SVD files can simply refer to another set of enumerated // values in the same register. // See: https://www.keil.com/pack/doc/CMSIS/SVD/html/elem_registers.html#elem_enumeratedValues - enumeratedValues := fieldEl.EnumeratedValues - if enumeratedValues.DerivedFrom != "" { - parts := strings.Split(enumeratedValues.DerivedFrom, ".") - if len(parts) == 1 { - found := false - for _, otherFieldEl := range fieldEls { - if otherFieldEl.EnumeratedValues.Name == parts[0] { - found = true - enumeratedValues = otherFieldEl.EnumeratedValues + for i := range fieldEl.EnumeratedValues { + enumeratedValues := &fieldEl.EnumeratedValues[i] + if enumeratedValues.DerivedFrom != "" { + parts := strings.Split(enumeratedValues.DerivedFrom, ".") + if len(parts) == 1 { + found := false + for _, otherFieldEl := range fieldEls { + for i := range otherFieldEl.EnumeratedValues { + otherEnum := &otherFieldEl.EnumeratedValues[i] + if otherEnum.Name == parts[0] { + found = true + *enumeratedValues = *otherEnum + } + } } + if !found { + fmt.Fprintf(os.Stderr, "Warning: could not find enumeratedValue.derivedFrom of %s for register field %s\n", enumeratedValues.DerivedFrom, fieldName) + } + } else { + // The derivedFrom attribute may also point to enumerated values + // in other registers and even peripherals, but this feature + // isn't often used in SVD files. + fmt.Fprintf(os.Stderr, "TODO: enumeratedValue.derivedFrom to a different register: %s\n", enumeratedValues.DerivedFrom) } - if !found { - fmt.Fprintf(os.Stderr, "Warning: could not find enumeratedValue.derivedFrom of %s for register field %s\n", enumeratedValues.DerivedFrom, fieldName) - } - } else { - // The derivedFrom attribute may also point to enumerated values - // in other registers and even peripherals, but this feature - // isn't often used in SVD files. - fmt.Fprintf(os.Stderr, "TODO: enumeratedValue.derivedFrom to a different register: %s\n", enumeratedValues.DerivedFrom) } } @@ -678,76 +685,77 @@ func parseBitfields(groupName, regName string, fieldEls []*SVDField, bitfieldPre Value: 1 << lsb, }) } - for _, enumEl := range enumeratedValues.EnumeratedValue { - enumName := enumEl.Name - // Renesas has enum without actual values that we have to skip - if enumEl.Value == "" { - continue - } + for i := range fieldEl.EnumeratedValues { + for _, enumEl := range fieldEl.EnumeratedValues[i].EnumeratedValue { + enumName := enumEl.Name + // Renesas has enum without actual values that we have to skip + if enumEl.Value == "" { + continue + } - if strings.EqualFold(enumName, "reserved") || !validName.MatchString(enumName) { - continue - } - if !unicode.IsUpper(rune(enumName[0])) && !unicode.IsDigit(rune(enumName[0])) { - enumName = strings.ToUpper(enumName) - } - enumDescription := formatText(enumEl.Description) - var enumValue uint64 - var err error - if strings.HasPrefix(enumEl.Value, "0b") { - val := strings.TrimPrefix(enumEl.Value, "0b") - enumValue, err = strconv.ParseUint(val, 2, 64) - } else { - enumValue, err = strconv.ParseUint(enumEl.Value, 0, 64) - } - if err != nil { - if enumBitSpecifier.MatchString(enumEl.Value) { - // NXP and Renesas SVDs use the form #xx1x, #x0xx, etc for values - enumValue, err = strconv.ParseUint(strings.ReplaceAll(enumEl.Value[1:], "x", "0"), 2, 64) - if err != nil { - panic(err) - } + if strings.EqualFold(enumName, "reserved") || !validName.MatchString(enumName) { + continue + } + if !unicode.IsUpper(rune(enumName[0])) && !unicode.IsDigit(rune(enumName[0])) { + enumName = strings.ToUpper(enumName) + } + enumDescription := formatText(enumEl.Description) + var enumValue uint64 + var err error + if strings.HasPrefix(enumEl.Value, "0b") { + val := strings.TrimPrefix(enumEl.Value, "0b") + enumValue, err = strconv.ParseUint(val, 2, 64) } else { - panic(err) + enumValue, err = strconv.ParseUint(enumEl.Value, 0, 64) } - } - enumName = fmt.Sprintf("%s_%s%s_%s_%s", groupName, bitfieldPrefix, regName, fieldName, enumName) - - // Avoid duplicate values. Duplicate names with the same value are - // allowed, but the same name with a different value is not. Instead - // of trying to work around those cases, remove the value entirely - // as there is probably not one correct answer in such a case. - // For example, SVD files from NXP have enums limited to 20 - // characters, leading to lots of duplicates when these enum names - // are long. Nothing here can really fix those cases. - previousEnumValue, seenBefore := enumSeen[enumName] - if seenBefore { - if previousEnumValue < 0 { - // There was a mismatch before, ignore all equally named fields. - continue + if err != nil { + if enumBitSpecifier.MatchString(enumEl.Value) { + // NXP and Renesas SVDs use the form #xx1x, #x0xx, etc for values + enumValue, err = strconv.ParseUint(strings.ReplaceAll(enumEl.Value[1:], "x", "0"), 2, 64) + if err != nil { + panic(err) + } + } else { + panic(err) + } } - if int64(enumValue) != previousEnumValue { - // There is a mismatch. Mark it as such, and remove the - // existing enum bitfield value. - enumSeen[enumName] = -1 - for i, field := range fields { - if field.Name == enumName { - fields = append(fields[:i], fields[i+1:]...) - break + enumName = fmt.Sprintf("%s_%s%s_%s_%s", groupName, bitfieldPrefix, regName, fieldName, enumName) + + // Avoid duplicate values. Duplicate names with the same value are + // allowed, but the same name with a different value is not. Instead + // of trying to work around those cases, remove the value entirely + // as there is probably not one correct answer in such a case. + // For example, SVD files from NXP have enums limited to 20 + // characters, leading to lots of duplicates when these enum names + // are long. Nothing here can really fix those cases. + previousEnumValue, seenBefore := enumSeen[enumName] + if seenBefore { + if previousEnumValue < 0 { + // There was a mismatch before, ignore all equally named fields. + continue + } + if int64(enumValue) != previousEnumValue { + // There is a mismatch. Mark it as such, and remove the + // existing enum bitfield value. + enumSeen[enumName] = -1 + for i, field := range fields { + if field.Name == enumName { + fields = append(fields[:i], fields[i+1:]...) + break + } } } + continue } - continue - } - enumSeen[enumName] = int64(enumValue) + enumSeen[enumName] = int64(enumValue) - fields = append(fields, Constant{ - Name: enumName, - Description: enumDescription, - Value: enumValue, - }) + fields = append(fields, Constant{ + Name: enumName, + Description: enumDescription, + Value: enumValue, + }) + } } - return true }) } From f9650e365635c6a4c528bcd322309ccc7b1265ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20Teichgr=C3=A4ber?= Date: Sun, 25 May 2025 03:02:27 +0200 Subject: [PATCH 06/11] tools/gen-device-svd: orderPeripherals: sort peripherals of same group with larger number of registers/bitfields first In group "TIM" there may be general purpose timers like TIM16 and advanced timers like TIM1. The advanced peripheral may contain a larger number of registers than the general purpose ones. TIM1 may contain CCR1..CCR4, SMCR and OR1, while TIM16 only knows about CCR1. Unfortunately in some SVDs, like the one for stm32g031, TIM16 is defined before TIM1. Since register and bitfield constants are generated taking only the first peripheral of a group into account, the resulting .go file may lack definitions for e.g. CCR2..CCR4, SMCR, and OR1. This change adjusts orderPeripherals so that, to stay with the example, a peripheral like TIM1 will be moved in front of TIM16, resulting in an output file containing the larger set of definitions. --- tools/gen-device-svd/gen-device-svd.go | 46 ++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/tools/gen-device-svd/gen-device-svd.go b/tools/gen-device-svd/gen-device-svd.go index 30117ce5df..a1e748868a 100755 --- a/tools/gen-device-svd/gen-device-svd.go +++ b/tools/gen-device-svd/gen-device-svd.go @@ -527,6 +527,7 @@ func orderPeripherals(input []SVDPeripheral) []*SVDPeripheral { for i := range input { tryProcess(&input[i]) } + orderPeripheralsByNumBitfields(sortedPeripherals) // missingBasePeripherals may still contain unordered entries; // repeat the process until missingBasePeripheral does not change anymore. @@ -551,6 +552,51 @@ func orderPeripherals(input []SVDPeripheral) []*SVDPeripheral { return sortedPeripherals } +func orderPeripheralsByNumBitfields(list []*SVDPeripheral) { + seenGroup := make(map[string]struct{}) + for i, p := range list { + groupName := p.GroupName + if groupName == "" || p.DerivedFrom != "" { + continue + } + if _, ok := seenGroup[groupName]; ok { + continue + } + iMax, nMax := -1, p.bitfieldCount() + for j, p2 := range list[i+1:] { + if p2.GroupName != groupName || p2.DerivedFrom != "" { + continue + } + if n2 := p2.bitfieldCount(); n2 > nMax { + iMax = i + 1 + j + nMax = n2 + } + } + if iMax != -1 { + pMax := list[iMax] + // swap peripherals + copy(list[i+1:iMax+1], list[i:iMax]) + list[i] = pMax + seenGroup[groupName] = struct{}{} + } + } +} + +func (p *SVDPeripheral) bitfieldCount() int { + n := 0 + for _, r := range p.Registers { + for _, f := range r.Fields { + dim := decodeDim(f.Dim) + if dim > 0 { + n += dim + } else { + n++ + } + } + } + return n +} + func addInterrupt(interrupts map[string]*Interrupt, name, interruptName string, index int, description string) { if _, ok := interrupts[name]; ok { if interrupts[name].Value != index { From cf41f4f89acde3ccb438ab6add4ca1ed536d9e5c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20Teichgr=C3=A4ber?= Date: Tue, 27 May 2025 09:24:51 +0200 Subject: [PATCH 07/11] tools/gen-device-svd: SVDEnumeration: support isDefault Recent SVD files created by stm32-rs use "isDefault" in enumeratedValue elements for purposes like the Div1 enum for clock prescaler registers without specifying a specific value. Previously, these enumeratedValues would be skipped because of the enumEl.Value == 0 condition, and the corresponding const definitions like "RCC_CFGR2_PPRE2_Div1 = 0x0" would be missing from the resulting .go files, so existing code relying on these constants would not compile anymore. This change adds a utility type enumDefaultResolver that helps finding an actual value that is unused by the enumeratedValues that are defined for the field. More examples for values marked as "isDefault", along with their resolved values: DAC_CR_WAVE1_Triangle => 2 IWDG_PR_PR_DivideBy256 => 6 DAC_CR_MAMP2_Amp4095 => 0xb --- tools/gen-device-svd/gen-device-svd.go | 172 +++++++++++++++++++------ 1 file changed, 133 insertions(+), 39 deletions(-) diff --git a/tools/gen-device-svd/gen-device-svd.go b/tools/gen-device-svd/gen-device-svd.go index a1e748868a..b6acd5c9a2 100755 --- a/tools/gen-device-svd/gen-device-svd.go +++ b/tools/gen-device-svd/gen-device-svd.go @@ -10,6 +10,7 @@ import ( "os" "path/filepath" "regexp" + "slices" "sort" "strconv" "strings" @@ -92,6 +93,7 @@ type SVDEnumeration struct { Name string `xml:"name"` Description string `xml:"description"` Value string `xml:"value"` + IsDefault bool `xml:"isDefault"` } `xml:"enumeratedValue"` } @@ -629,6 +631,7 @@ func addInterrupt(interrupts map[string]*Interrupt, name, interruptName string, func parseBitfields(groupName, regName string, fieldEls []*SVDField, bitfieldPrefix string) ([]Constant, []Bitfield) { var fields []Constant var bitfields []Bitfield + var enumDefault enumDefaultResolver enumSeen := map[string]int64{} for _, fieldEl := range fieldEls { // Some bitfields (like the STM32H7x7) contain invalid bitfield @@ -732,12 +735,10 @@ func parseBitfields(groupName, regName string, fieldEls []*SVDField, bitfieldPre }) } for i := range fieldEl.EnumeratedValues { + enumDefault.reset(1<<(msb+1-lsb) - 1) + fields0Pos := len(fields) for _, enumEl := range fieldEl.EnumeratedValues[i].EnumeratedValue { enumName := enumEl.Name - // Renesas has enum without actual values that we have to skip - if enumEl.Value == "" { - continue - } if strings.EqualFold(enumName, "reserved") || !validName.MatchString(enumName) { continue @@ -745,7 +746,27 @@ func parseBitfields(groupName, regName string, fieldEls []*SVDField, bitfieldPre if !unicode.IsUpper(rune(enumName[0])) && !unicode.IsDigit(rune(enumName[0])) { enumName = strings.ToUpper(enumName) } + enumName = fmt.Sprintf("%s_%s%s_%s_%s", groupName, bitfieldPrefix, regName, fieldName, enumName) enumDescription := formatText(enumEl.Description) + + if enumEl.IsDefault { + enumDefault.setDefaultAction(func(value uint64) { + if value == 0 { + // put zero value in front of other constants + fields = slices.Insert(fields, fields0Pos, Constant{}) + appendConstant(fields[:fields0Pos], enumName, enumDescription, value, enumSeen) + } else { + fields = appendConstant(fields, enumName, enumDescription, value, enumSeen) + } + }) + continue + } + + // Renesas has enum without actual values that we have to skip + if enumEl.Value == "" { + continue + } + var enumValue uint64 var err error if strings.HasPrefix(enumEl.Value, "0b") { @@ -765,42 +786,10 @@ func parseBitfields(groupName, regName string, fieldEls []*SVDField, bitfieldPre panic(err) } } - enumName = fmt.Sprintf("%s_%s%s_%s_%s", groupName, bitfieldPrefix, regName, fieldName, enumName) - - // Avoid duplicate values. Duplicate names with the same value are - // allowed, but the same name with a different value is not. Instead - // of trying to work around those cases, remove the value entirely - // as there is probably not one correct answer in such a case. - // For example, SVD files from NXP have enums limited to 20 - // characters, leading to lots of duplicates when these enum names - // are long. Nothing here can really fix those cases. - previousEnumValue, seenBefore := enumSeen[enumName] - if seenBefore { - if previousEnumValue < 0 { - // There was a mismatch before, ignore all equally named fields. - continue - } - if int64(enumValue) != previousEnumValue { - // There is a mismatch. Mark it as such, and remove the - // existing enum bitfield value. - enumSeen[enumName] = -1 - for i, field := range fields { - if field.Name == enumName { - fields = append(fields[:i], fields[i+1:]...) - break - } - } - } - continue - } - enumSeen[enumName] = int64(enumValue) - - fields = append(fields, Constant{ - Name: enumName, - Description: enumDescription, - Value: enumValue, - }) + enumDefault.collectValue(enumValue) + fields = appendConstant(fields, enumName, enumDescription, enumValue, enumSeen) } + enumDefault.resolve() } return true }) @@ -808,6 +797,111 @@ func parseBitfields(groupName, regName string, fieldEls []*SVDField, bitfieldPre return fields, bitfields } +func appendConstant(fields []Constant, enumName, enumDescription string, enumValue uint64, enumSeen map[string]int64) []Constant { + // Avoid duplicate values. Duplicate names with the same value are + // allowed, but the same name with a different value is not. Instead + // of trying to work around those cases, remove the value entirely + // as there is probably not one correct answer in such a case. + // For example, SVD files from NXP have enums limited to 20 + // characters, leading to lots of duplicates when these enum names + // are long. Nothing here can really fix those cases. + previousEnumValue, seenBefore := enumSeen[enumName] + if seenBefore { + if previousEnumValue < 0 { + // There was a mismatch before, ignore all equally named fields. + return fields + } + if int64(enumValue) != previousEnumValue { + // There is a mismatch. Mark it as such, and remove the + // existing enum bitfield value. + enumSeen[enumName] = -1 + for i, field := range fields { + if field.Name == enumName { + fields = append(fields[:i], fields[i+1:]...) + break + } + } + } + return fields + } + enumSeen[enumName] = int64(enumValue) + + fields = append(fields, Constant{ + Name: enumName, + Description: enumDescription, + Value: enumValue, + }) + return fields +} + +// enumDefaultResolver helps determine the actual numeric value for an +// enumeratedValue marked as the default (i.e., where "isDefault" is set). +// +// Some SVD files use "isDefault" to indicate a fallback value (e.g., Div1 in +// clock prescaler registers) without specifying the exact value when it's not +// critical. This type is used to collect all defined enumValues, and once +// collection is complete, derive a sensible default value that does not conflict +// with any explicitly defined ones. +// +// Typically, it prefers zero as a default if available; otherwise, it will +// choose a suitable unused value below the field's maximum. +type enumDefaultResolver struct { + values []uint64 + maxValue uint64 + handleDefault func(value uint64) +} + +func (dr *enumDefaultResolver) reset(maxValue uint64) { + dr.values = dr.values[:0] + dr.maxValue = maxValue + dr.handleDefault = nil +} + +func (dr *enumDefaultResolver) setDefaultAction(action func(v uint64)) { + dr.handleDefault = action +} + +func (dr *enumDefaultResolver) collectValue(value uint64) { + dr.values = append(dr.values, value) +} + +// resolve tries to find an actual value for the enumerated Value +// marked as default. +func (dr *enumDefaultResolver) resolve() { + if dr.handleDefault == nil { + return + } + list := dr.values + n := len(list) + if n == 0 { + return + } + slices.Sort(list) + + var value uint64 + // try to use zero as default value + if list[0] == 0 { + // not available, now try the highest value +1 + largest := list[n-1] + if largest < dr.maxValue { + value = largest + 1 + } else { + value = 1 + // not available, now lookup the first free value + for _, enumValue := range list[1:] { + if value < enumValue { + break + } + value = enumValue + 1 + if value == dr.maxValue { + return + } + } + } + } + dr.handleDefault(value) +} + type Register struct { element *SVDRegister baseAddress uint64 From 6a7d7dfd6ca89e0c469768308c6d2c2a7f1c5dba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20Teichgr=C3=A4ber?= Date: Tue, 27 May 2025 13:39:43 +0200 Subject: [PATCH 08/11] tools/gen-device-svd: support derivedFrom attribute at field level This ensures that some more constants are included in the .go files that would otherwise be skipped (like e.g. ADC_SMPR2_SMP1_Cycles* of some STM32 devices), which prevented compilation of some programs. To avoid extending a lot of func argument lists, and since there is no context.Context in use yet, this change introduces a global derivationContext. --- tools/gen-device-svd/gen-device-svd.go | 105 +++++++++++++++++++++++++ 1 file changed, 105 insertions(+) diff --git a/tools/gen-device-svd/gen-device-svd.go b/tools/gen-device-svd/gen-device-svd.go index b6acd5c9a2..eea86141e7 100755 --- a/tools/gen-device-svd/gen-device-svd.go +++ b/tools/gen-device-svd/gen-device-svd.go @@ -62,6 +62,7 @@ type SVDRegister struct { } type SVDField struct { + DerivedFrom string `xml:"derivedFrom,attr"` Name string `xml:"name"` Description string `xml:"description"` Dim *string `xml:"dim"` @@ -374,6 +375,7 @@ func readSVD(path, sourceURL string) (*Device, error) { // comes later in the file. To make sure this works, sort the peripherals if // needed. orderedPeripherals := orderPeripherals(device.Peripherals) + globalDerivationCtx.peripherals = orderedPeripherals for _, periphEl := range orderedPeripherals { description := formatText(periphEl.Description) @@ -634,6 +636,14 @@ func parseBitfields(groupName, regName string, fieldEls []*SVDField, bitfieldPre var enumDefault enumDefaultResolver enumSeen := map[string]int64{} for _, fieldEl := range fieldEls { + + if fieldEl.DerivedFrom != "" { + err := globalDerivationCtx.deriveField(fieldEl, fieldEls) + if err != nil { + fmt.Fprintf(os.Stderr, "unable to derive field %q from %q: %v\n", fieldEl.Name, fieldEl.DerivedFrom, err.Error()) + } + } + // Some bitfields (like the STM32H7x7) contain invalid bitfield // names like "CNT[31]". Replace invalid characters with "_" when // needed. @@ -797,6 +807,101 @@ func parseBitfields(groupName, regName string, fieldEls []*SVDField, bitfieldPre return fields, bitfields } +var globalDerivationCtx derivationContext + +type derivationContext struct { + peripherals []*SVDPeripheral +} + +func (ctx *derivationContext) deriveField(fieldEl *SVDField, localFieldEls []*SVDField) error { + from := fieldEl.DerivedFrom + parts := strings.Split(from, ".") + srcName := parts[0] + var srcFieldEl *SVDField + switch len(parts) { + case 3, 4: + src, err := ctx.lookupGlobal(parts) + if err != nil { + return err + } + srcFieldEl = src + case 1: + // resolve locally, in current register + for _, f := range localFieldEls { + if f == fieldEl { + continue + } + if f.Name == srcName { + srcFieldEl = f + break + } + } + if srcFieldEl == nil { + return fmt.Errorf("not found") + } + default: + return fmt.Errorf("cannot decode source path") + } + + // copy enumeratedValues from source to current field + if fieldEl.DimIndex == nil && strings.Contains(fieldEl.Name, "%s") { + fieldEl.DimIndex = srcFieldEl.DimIndex + fieldEl.DimIncrement = srcFieldEl.DimIncrement + fieldEl.Dim = srcFieldEl.Dim + } + if fieldEl.Description == "" { + fieldEl.Description = srcFieldEl.Description + } + if fieldEl.BitWidth == nil { + fieldEl.BitWidth = srcFieldEl.BitWidth + } + if fieldEl.BitOffset == nil { + fieldEl.BitOffset = srcFieldEl.BitOffset + } + if fieldEl.BitRange == nil { + fieldEl.BitRange = srcFieldEl.BitRange + } + + fieldEl.EnumeratedValues = srcFieldEl.EnumeratedValues + return nil +} + +func (ctx *derivationContext) lookupGlobal(path []string) (*SVDField, error) { + curPath := path[:1] + for _, p := range ctx.peripherals { + if p.Name == path[0] { + if len(path) == 4 { + curPath = path[:2] + for _, c := range p.Clusters { + if c.Name == path[1] { + return ctx.lookupFieldInRegs(path[2:], c.Registers, curPath) + } + } + return nil, fmt.Errorf("cluster not found: %q", path[2]) + + } + return ctx.lookupFieldInRegs(path[1:], p.Registers, curPath) + } + } + return nil, fmt.Errorf("peripheral not found: %s", path[0]) +} + +func (ctx *derivationContext) lookupFieldInRegs(path []string, registers []*SVDRegister, curPath []string) (*SVDField, error) { + curPath = curPath[:len(curPath)+1] + for _, r := range registers { + if r.Name == path[0] { + curPath = curPath[:len(curPath)+1] + for _, f := range r.Fields { + if f.Name == path[1] { + return f, nil + } + } + return nil, fmt.Errorf("field not found: %q", strings.Join(curPath, ".")) + } + } + return nil, fmt.Errorf("register not found: %q", strings.Join(curPath, ".")) +} + func appendConstant(fields []Constant, enumName, enumDescription string, enumValue uint64, enumSeen map[string]int64) []Constant { // Avoid duplicate values. Duplicate names with the same value are // allowed, but the same name with a different value is not. Instead From af3a4b11bd9dbb1bd7f9a7c2f6955ff9b6c36ba6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20Teichgr=C3=A4ber?= Date: Mon, 23 Feb 2026 00:25:06 +0100 Subject: [PATCH 09/11] tools/gen-device-svd: tweak: stm32: ensure USART_ISR_TXE/TXFNF are present --- tools/gen-device-svd/gen-device-svd.go | 5 +- tools/gen-device-svd/tweak.go | 85 ++++++++++++++++++++++++++ 2 files changed, 89 insertions(+), 1 deletion(-) create mode 100644 tools/gen-device-svd/tweak.go diff --git a/tools/gen-device-svd/gen-device-svd.go b/tools/gen-device-svd/gen-device-svd.go index eea86141e7..706c80ce44 100755 --- a/tools/gen-device-svd/gen-device-svd.go +++ b/tools/gen-device-svd/gen-device-svd.go @@ -1322,9 +1322,12 @@ var ( ) `)) + pkgName := filepath.Base(strings.TrimRight(outdir, "/")) + tweakDevice(device, pkgName) + err = t.Execute(w, map[string]interface{}{ "device": device, - "pkgName": filepath.Base(strings.TrimRight(outdir, "/")), + "pkgName": pkgName, "interruptMax": maxInterruptValue, "interruptSystem": interruptSystem, "interruptHandlers": interruptHandlers, diff --git a/tools/gen-device-svd/tweak.go b/tools/gen-device-svd/tweak.go new file mode 100644 index 0000000000..9fdd196951 --- /dev/null +++ b/tools/gen-device-svd/tweak.go @@ -0,0 +1,85 @@ +package main + +import ( + "slices" +) + +func tweakDevice(d *Device, pkgName string) { + if pkgName != "stm32" { + // no-op for device types that do not need tweaks + return + } + + for _, p := range d.Peripherals { + switch p.GroupName { + case "USART": + isr := p.lookupRegister("ISR") + if isr == nil { + continue + } + + // Some of the upstream SVD files, like the one for stm32wl5x_cm4, + // lack FIFO enabled variants of the USART ISR register, + // even if the register manual defines them. To make sure + // that TXFNF is not missing from the generated .go files, + // we add TXFNF here in case FIFOEN is present. + if p.lookupRegister("CR1").hasBitfield("FIFOEN") { + stm32EnsureBit(isr, "TXFNF", "TXE", "USART_ISR_") + } + + // Svdtools handles the presence of alternate USART ISR registers, + // like in case of the stm32l4r5, adjusting names like "ISR_enabled" + // to "ISR", deleting "ISR_disabled" or "ISR_ALTERNATE" register definitions + // from the SVD. + // As this would result in USART_ISR_TXE definitions missing in the + // generated .go file, a constant for TXE is added here + // in case TXFNF is defined. + stm32EnsureBit(isr, "TXE", "TXFNF", "USART_ISR_") + } + } +} + +func stm32EnsureBit(reg *PeripheralField, want, have, prefix string) { + iWant := -1 + iHave := -1 + wantConst := prefix + want + haveConst := prefix + have + for i := range reg.Constants { + f := ®.Constants[i] + if f.Name == wantConst { + iWant = i + break + } + if f.Name == haveConst { + iHave = i + break + } + } + if iHave != -1 && iWant == -1 { + iWant = iHave + 1 + reg.Constants = slices.Insert(reg.Constants, iWant, reg.Constants[iHave]) + reg.Constants[iWant].Name = wantConst + reg.Constants[iWant].Description = "Bit " + want + ". (added by gen-device-svd)" + } +} + +func (p *Peripheral) lookupRegister(name string) *PeripheralField { + for _, r := range p.Registers { + if r.Name == name { + return r + } + } + return nil +} + +func (r *PeripheralField) hasBitfield(name string) bool { + if r == nil { + return false + } + for i := range r.Bitfields { + if r.Bitfields[i].Name == name { + return true + } + } + return false +} From 00aa4139632dbd853fa1fa76ba952b6b2240116d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20Teichgr=C3=A4ber?= Date: Mon, 23 Feb 2026 00:33:54 +0100 Subject: [PATCH 10/11] tools/gen-device-svd: stm32: ensure CCMR*_Output alternate registers are sorted first --- tools/gen-device-svd/tweak.go | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/tools/gen-device-svd/tweak.go b/tools/gen-device-svd/tweak.go index 9fdd196951..6099a9c0ea 100644 --- a/tools/gen-device-svd/tweak.go +++ b/tools/gen-device-svd/tweak.go @@ -2,6 +2,7 @@ package main import ( "slices" + "strings" ) func tweakDevice(d *Device, pkgName string) { @@ -12,6 +13,15 @@ func tweakDevice(d *Device, pkgName string) { for _, p := range d.Peripherals { switch p.GroupName { + case "TIM": + // SVDs like stm32l4r5.svd define CCMR*_Input and _Output + // alternate registers, with _Input sorted before _Output. + // This would result in the _Output fields missing from the + // TIM_type struct definition, hence compilation would fail. + // Therefore we adjust the order of these alternate registers + // accordingly. + stm32EnsureCCMROrder(p.Registers) + case "USART": isr := p.lookupRegister("ISR") if isr == nil { @@ -39,6 +49,21 @@ func tweakDevice(d *Device, pkgName string) { } } +func stm32EnsureCCMROrder(registers []*PeripheralField) { + for i, r := range registers { + if i > 0 { + prev := registers[i-1] + if r.Address == prev.Address { + // alternate field + if strings.HasPrefix(prev.Name, "CCMR") && strings.HasPrefix(r.Name, "CCMR") && strings.HasSuffix(r.Name, "_Output") { + // swap register pointers + registers[i-1], registers[i] = r, prev + } + } + } + } +} + func stm32EnsureBit(reg *PeripheralField, want, have, prefix string) { iWant := -1 iHave := -1 From 36f0ce3b1b76ebf615e15e7714b389ceac594b69 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20Teichgr=C3=A4ber?= Date: Mon, 23 Feb 2026 00:35:05 +0100 Subject: [PATCH 11/11] tools/gen-device-svd: stm32: add IWDG peripheral alias if SVD defines IWDG1 --- tools/gen-device-svd/gen-device-svd.go | 4 ++++ tools/gen-device-svd/tweak.go | 14 ++++++++++++++ 2 files changed, 18 insertions(+) diff --git a/tools/gen-device-svd/gen-device-svd.go b/tools/gen-device-svd/gen-device-svd.go index 706c80ce44..ef9dedff97 100755 --- a/tools/gen-device-svd/gen-device-svd.go +++ b/tools/gen-device-svd/gen-device-svd.go @@ -129,6 +129,7 @@ type Interrupt struct { type Peripheral struct { Name string + Alias string GroupName string BaseAddress uint64 Description string @@ -1317,6 +1318,9 @@ var ( {{- end}} {{- end}} {{.Name}} = (*{{.GroupName}}_Type)(unsafe.Pointer(uintptr(0x{{printf "%x" .BaseAddress}}))) + {{- if .Alias}} + {{.Alias}} = {{.Name}} + {{- end}} {{- "\n"}} {{- end}} ) diff --git a/tools/gen-device-svd/tweak.go b/tools/gen-device-svd/tweak.go index 6099a9c0ea..8ee505da14 100644 --- a/tools/gen-device-svd/tweak.go +++ b/tools/gen-device-svd/tweak.go @@ -11,6 +11,12 @@ func tweakDevice(d *Device, pkgName string) { return } + // Source file machine_stm32_iwdg.go relies on the presence of + // a register IWDG. On some devices, though, like the h723, + // there are two registers, IWDG1 and IWDG2. In this case we + // define an alias IWDG for IWDG1. + addUnnumberedAlias(d, "IWDG", "IWDG1") + for _, p := range d.Peripherals { switch p.GroupName { case "TIM": @@ -49,6 +55,14 @@ func tweakDevice(d *Device, pkgName string) { } } +func addUnnumberedAlias(d *Device, dest, src string) { + if _, ok := d.PeripheralDict[dest]; !ok { + if p := d.PeripheralDict[src]; p != nil { + p.Alias = dest + } + } +} + func stm32EnsureCCMROrder(registers []*PeripheralField) { for i, r := range registers { if i > 0 {