| /** | |
| * Copyright (c) 2020 Raspberry Pi (Trading) Ltd. | |
| * | |
| * SPDX-License-Identifier: BSD-3-Clause | |
| */ | |
| #include <exception> | |
| #include <iostream> | |
| #include <fstream> | |
| #include <regex> | |
| #include <map> | |
| #include <cassert> | |
| #include <cstdint> | |
| #include "nlohmann/json.hpp" | |
| // missing __builtins on windows | |
| #if defined(_MSC_VER) && !defined(__clang__) | |
| # include <intrin.h> | |
| # define __builtin_popcount __popcnt | |
| static __forceinline int __builtin_ctz(unsigned x) { | |
| unsigned long r; | |
| _BitScanForward(&r, x); | |
| return (int)r; | |
| } | |
| #endif | |
| using std::cout; | |
| using std::cerr; | |
| using json = nlohmann::json; | |
| // todo values? | |
| static void usage() { | |
| std::cerr << "usage: otp_header_parser <otp_data.h filename> <output header filename>" << std::endl; | |
| } | |
| enum { | |
| ERROR_ARGS = 1, | |
| ERROR_INPUT = 2, | |
| ERROR_UNKNOWN = 3, | |
| }; | |
| struct otp_field { | |
| otp_field() = default; | |
| std::string name; | |
| uint32_t mask = 0; | |
| std::string description; | |
| NLOHMANN_DEFINE_TYPE_INTRUSIVE(otp_field, name, mask, description) | |
| }; | |
| struct otp_reg { | |
| otp_reg() = default; | |
| uint32_t row = 0xffffffff; | |
| uint32_t mask = 0; | |
| int redundancy = 1; | |
| bool ecc = false; | |
| bool crit = false; | |
| std::string seq_prefix; | |
| int seq_length = 0; | |
| int seq_index = 0; | |
| std::string description; | |
| std::string name; | |
| std::vector<otp_field> fields; | |
| NLOHMANN_DEFINE_TYPE_INTRUSIVE(otp_reg, row, mask, redundancy, ecc, crit, seq_prefix, seq_length, seq_index, description, name, fields) | |
| }; | |
| std::ostream &operator<<(std::ostream &outs, const otp_reg &e) { | |
| auto &x = outs << "row=" << std::hex << e.row << ", mask=" << e.mask << ", ecc=" << e.ecc << ", rdncy=" << std::dec << e.redundancy; | |
| if (e.description.empty()) { | |
| return x; | |
| } else { | |
| return x << ", '" << e.description << "'"; | |
| } | |
| } | |
| std::ostream &operator<<(std::ostream &outs, const std::pair<std::string, otp_reg> &p) { | |
| const auto&e = p.second; | |
| return outs << p.first << "(" << e << ")"; | |
| } | |
| std::ostream &operator<<(std::ostream &outs, const otp_field &f) { | |
| auto &x = outs << f.name << " mask=" << std::hex << f.mask; | |
| if (f.description.empty()) { | |
| return x; | |
| } else { | |
| return x << ", '" << f.description << "'"; | |
| } | |
| } | |
| std::map<std::string, otp_reg> otp_regs; | |
| bool starts_with(std::string const &fullString, std::string const &prefix) { | |
| if (fullString.length() >= prefix.length()) { | |
| return (0 == fullString.compare(0, prefix.length(), prefix)); | |
| } else { | |
| return false; | |
| } | |
| } | |
| bool ends_with(std::string const &fullString, std::string const &ending) { | |
| if (fullString.length() >= ending.length()) { | |
| return (0 == fullString.compare(fullString.length() - ending.length(), ending.length(), ending)); | |
| } else { | |
| return false; | |
| } | |
| } | |
| std::string trim(std::string str) | |
| { | |
| str.erase(str.find_last_not_of(' ')+1); | |
| str.erase(0, str.find_first_not_of(' ')); | |
| return str; | |
| } | |
| bool valid_description(std::string description) { | |
| if (description.empty()) return false; | |
| if (description == "None") return false; | |
| return true; | |
| } | |
| int main(int argc, char **argv) { | |
| if (argc < 2) { | |
| usage(); | |
| return ERROR_ARGS; | |
| } | |
| try { | |
| std::ifstream infile(argv[1]); | |
| std::string line; | |
| std::regex define_u_regex(R"(#define[\s]+([^\s]*)[\s]+_u\(0x(.*)\))"); | |
| std::regex reg_regex(R"(// Register[\s]+:[\s]+(.*)[\s]*)"); | |
| std::regex field_regex(R"(// Field[\s]+:[\s]+(.*)[\s]*)"); | |
| std::regex desc_regex("// Description[\\s]+:[\\s]+(.*)"); | |
| std::regex ecc_regex("//.*\\(ECC\\).*"); | |
| std::regex rbit_regex(R"(//.*\(RBIT-\([0-9]\)\).*)"); | |
| std::regex rn_regex(".*_R[0-9]$"); | |
| std::regex zeroth_regex("([a-zA-Z_0-9]*[a-zA-Z_])0$"); | |
| enum { | |
| REGISTER, | |
| FIELD, | |
| NONE | |
| } type = NONE; | |
| bool ecc; | |
| std::string reg_name; | |
| std::string field_name; | |
| std::string comment; | |
| int expected_redundancy = 1; | |
| otp_reg reg; | |
| otp_field field; | |
| while (std::getline(infile, line)) { | |
| // std::cout << "LINE " << line << std::endl; | |
| std::smatch smatch; | |
| if (starts_with(line, "// ======")) { | |
| // start of a new register (or end of file) | |
| if (reg.mask) { | |
| if (reg.redundancy != expected_redundancy) { | |
| cerr << reg_name << " redundancy count mismatch " << reg.redundancy << " != " << expected_redundancy << std::endl; | |
| return ERROR_INPUT; | |
| } | |
| if (!field.name.empty()) { | |
| reg.fields.push_back(field); | |
| } | |
| otp_regs.emplace(reg_name, reg); | |
| reg = otp_reg(); | |
| } | |
| expected_redundancy = 1; | |
| reg_name = ""; | |
| field.name = ""; | |
| type = NONE; | |
| } else if (starts_with(line, "// ------")) { | |
| if (type == FIELD && !field.name.empty()) { | |
| reg.fields.push_back(field); | |
| } | |
| // we are between regs/fields | |
| type = NONE; | |
| } else if (std::regex_match(line, smatch, reg_regex)) { | |
| type = REGISTER; | |
| field_name = ""; | |
| reg_name = smatch[1].str(); | |
| reg.name = reg_name; | |
| } else if (std::regex_match(line, smatch, field_regex)) { | |
| field_name = smatch[1]; | |
| if (!starts_with(field_name, reg_name+"_")) { | |
| cerr << "ERROR: field name " << field_name << " is not prefixed with expected " << reg_name << "_" << std::endl; | |
| return ERROR_INPUT; | |
| } | |
| type = FIELD; | |
| field.name = field_name.substr(reg_name.length() + 1); | |
| field.mask = 0; | |
| field.description = ""; | |
| } else if (std::regex_match( line, smatch, define_u_regex)) { | |
| const auto& define_name = smatch[1].str(); | |
| const auto& define_hex = smatch[2].str(); | |
| if (reg_name.empty()) { | |
| std::cerr << "Got define '" << define_name << "' outside of register" << std::endl; | |
| return ERROR_INPUT; | |
| } | |
| if (!starts_with(define_name, reg_name + "_")) { | |
| std::cerr << "Got define '" << define_name << "' which doesn't start with " << reg_name << std::endl; | |
| return ERROR_INPUT; | |
| } | |
| uint32_t define_value = std::stoul(define_hex, nullptr, 16); | |
| if (define_name == reg_name + "_ROW") { | |
| reg.row = define_value; | |
| } else if (type == REGISTER && define_name == reg_name + "_BITS") { | |
| reg.mask = define_value; | |
| } else if (type == FIELD && define_name == field_name + "_BITS") { | |
| field.mask = define_value; | |
| } else if (ends_with(define_name, "_BITS")) { | |
| cout << " " << define_name << " : " << define_hex << std::endl; | |
| } | |
| } else if (std::regex_match( line, smatch, desc_regex)) { | |
| if (type == REGISTER) { | |
| reg.description = smatch[1]; | |
| } else if (type == FIELD) { | |
| field.description = smatch[1]; | |
| } | |
| } else if (starts_with(line, "// ")) { | |
| if (type == REGISTER) { | |
| reg.description += " " + trim(line.substr(2)); | |
| } else if (type == FIELD) { | |
| field.description += " " + trim(line.substr(2)); | |
| } | |
| } | |
| if (std::regex_match(line, smatch, ecc_regex)) { | |
| if (type == REGISTER) { | |
| reg.ecc = true; | |
| } else { | |
| cerr << "ERROR: found (ECC) directive outside of register description" << std::endl; | |
| return ERROR_INPUT; | |
| } | |
| } | |
| if (std::regex_match(line, smatch, rbit_regex)) { | |
| if (type == REGISTER) { | |
| expected_redundancy = smatch[1].str()[0] - '0'; | |
| } else { | |
| cerr << "ERROR: found (RBIT) directive outside of register description" << std::endl; | |
| return ERROR_INPUT; | |
| } | |
| } | |
| } | |
| for (auto it = otp_regs.cbegin(); it != otp_regs.cend();) { | |
| const auto &name = it->first; | |
| std::smatch smatch; | |
| if (std::regex_match(name, smatch, rn_regex)) { | |
| int n = name[name.length() - 1] - '0'; | |
| auto it2 = otp_regs.find(name.substr(0, name.length() - 3)); | |
| if (it2 != otp_regs.end()) { | |
| if (it->second.row != it2->second.row + n) { | |
| cerr << "ERROR " << *it << " has redundancy relationship to " << *it2 | |
| << " but offsets are wrong" << std::endl; | |
| // return ERROR_INPUT; | |
| } | |
| if (it2->second.redundancy != n) { | |
| // should appear in order | |
| cerr << "ERROR out of order redundant field " << name << std::endl; | |
| return ERROR_INPUT; | |
| } | |
| it2->second.redundancy++; | |
| otp_regs.erase(it++); | |
| if (it2->second.redundancy == 8) { | |
| // for the crit rows | |
| it2->second.crit = true; | |
| } | |
| continue; | |
| } | |
| } | |
| it++; | |
| } | |
| for (auto it = otp_regs.begin(); it != otp_regs.end(); it++) { | |
| const auto &name = it->first; | |
| std::smatch smatch; | |
| if (std::regex_match(name, smatch, zeroth_regex)) { | |
| auto prefix = smatch[1].str(); | |
| uint32_t relmask = 0; | |
| uint32_t relmask_nofields = 0; | |
| // cout << "HAHAH " << prefix << " from " << name << std::endl; | |
| std::vector<std::string> names; | |
| for (auto &e: otp_regs) { | |
| if (starts_with(e.first, prefix)) { | |
| // cout << e.first << std::endl; | |
| // cout << " '" << e.first.substr(prefix.length()) << "'" << std::endl; | |
| uint32_t index; | |
| try { | |
| index = std::stoul(e.first.substr(prefix.length())); | |
| } catch (std::invalid_argument &ex) { | |
| // not a numeric suffix | |
| continue; | |
| } | |
| if (e.second.row != it->second.row +index * it->second.redundancy) { | |
| cerr << "ERROR " << e << " has sequential relationship to " << *it | |
| << " but offsets are wrong" << std::endl; | |
| return ERROR_INPUT; | |
| } | |
| e.second.seq_index = (int)index; | |
| relmask |= 1u << index; | |
| if (e.second.fields.empty()) relmask_nofields |= 1u << index; | |
| names.push_back(e.first); | |
| } | |
| } | |
| assert(relmask); | |
| if (relmask > 1) { | |
| if (relmask_nofields & 1) { | |
| if (ends_with(prefix, "_")) prefix = prefix.substr(0, prefix.length()-1); | |
| if (relmask != relmask_nofields) { | |
| cerr << "ERROR " << prefix << " sequence a subset of members register which have fields" << std::endl; | |
| return ERROR_INPUT; | |
| } | |
| int len = __builtin_ctz(~relmask); | |
| if (relmask != (1u << len) - 1) { | |
| cerr << "ERROR " << prefix << " sequence is missing members" << std::endl; | |
| return ERROR_INPUT; | |
| } | |
| for (const auto &n: names) { | |
| otp_regs[n].seq_prefix = prefix; | |
| otp_regs[n].seq_length = len; | |
| } | |
| assert(len); | |
| } else { | |
| for (const auto &n: names) { | |
| otp_regs[n].seq_index = 0; // unset index | |
| } | |
| // cerr << "WARNING " << prefix << " is ignored as a sequential register as it has fields" << std::endl; | |
| } | |
| } else { | |
| cerr << "WARNING " << it->first << " ends in 0 but is not part of a sequnce" << std::endl; | |
| } | |
| } | |
| } | |
| assert(!reg.mask); // we should have seen a // ==== at the end | |
| for (const auto &e : otp_regs) { | |
| if (e.second.redundancy > 1 && e.second.ecc) { | |
| cerr << e.first << " has both redundancy and ECC" << std::endl; | |
| return ERROR_INPUT; | |
| } | |
| } | |
| std::ofstream out_file(argv[2]); | |
| #if CODE_OTP | |
| out_file << "// GENERATE FILE; DO NOT EDIT // " << std::endl << std::endl; | |
| out_file << "#pragma once" << std::endl; | |
| out_file << "std::vector<otp_reg> otp_reg_list = {" << std::endl; | |
| for (const auto &e : otp_regs) { | |
| const auto &r = e.second; | |
| out_file << " otp_reg(\"" << e.first << "\", 0x" << std::hex << r.row << ", 0x" << r.mask << ")"; | |
| if (r.ecc) { | |
| out_file << std::endl << " .with_ecc()"; | |
| } | |
| if (r.redundancy > 1) { | |
| out_file << std::endl << " .with_redundancy(" << std::dec << r.redundancy << ")"; | |
| } | |
| if (r.crit) { | |
| out_file << std::endl << " .with_crit()"; | |
| } | |
| if (valid_description(r.description)) { | |
| out_file << std::endl << " .with_description(\"" << r.description << "\")"; | |
| } | |
| if (!r.seq_prefix.empty()) { | |
| out_file << std::endl << " .with_sequence(\"" << std::dec << r.seq_prefix << "\", " << r.seq_index << ", " << r.seq_length << ")"; | |
| } | |
| for(const auto &f : r.fields) { | |
| out_file << std::endl << " .with_field(otp_field(\"" << f.name << "\", 0x" << std:: hex << f.mask; | |
| if (valid_description(f.description)) { | |
| out_file << ", \"" << f.description << "\""; | |
| } | |
| out_file << "))"; | |
| } | |
| out_file << ","; | |
| out_file << std::endl; | |
| } | |
| out_file << "};" << std::endl; | |
| #else | |
| json j; | |
| std::vector<otp_reg> otp_regs_vec; | |
| for(auto const& e: otp_regs) | |
| otp_regs_vec.push_back(e.second); | |
| j = otp_regs_vec; | |
| out_file << std::setw(4) << j << std::endl; | |
| #endif | |
| } catch (std::exception &e) { | |
| cerr << "ERROR: " << e.what() << "\n\n"; | |
| return ERROR_UNKNOWN; | |
| } | |
| return 0; | |
| } |