Skip to content

Commit 69e5193

Browse files
committed
added conversion from data to parameters for old HAL projects
1 parent 472b133 commit 69e5193

2 files changed

Lines changed: 312 additions & 0 deletions

File tree

src/netlist/persistent/netlist_serializer.cpp

Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -377,6 +377,137 @@ namespace hal
377377
return val;
378378
}
379379

380+
// Migrate "generic" data entries (old .hal format) to typed parameters.
381+
// Old parsers stored values as: "bit_vector" → unprefixed uppercase hex ("CAFE"),
382+
// "bit_value"/"std_logic" → single state char ("X", "0"). New format uses prefixes.
383+
void migrate_generic_data_to_parameters(DataContainer* c, const GateType* gate_type = nullptr)
384+
{
385+
std::vector<std::pair<std::string, std::string>> to_delete;
386+
387+
for (const auto& [key_tuple, value_tuple] : c->get_data_map())
388+
{
389+
if (std::get<0>(key_tuple) != "generic")
390+
continue;
391+
392+
const std::string& name = std::get<1>(key_tuple);
393+
const std::string& type_str = std::get<0>(value_tuple);
394+
const std::string& raw_value = std::get<1>(value_tuple);
395+
396+
std::string migrated_value = raw_value;
397+
Result<Parameter> inferred_decl = ERR("unrecognized data type '" + type_str + "'");
398+
bool skip = false;
399+
400+
if (type_str == "boolean")
401+
{
402+
inferred_decl = Parameter::Boolean(name, "");
403+
}
404+
else if (type_str == "integer")
405+
{
406+
inferred_decl = Parameter::Integer(name, "");
407+
}
408+
else if (type_str == "floating_point")
409+
{
410+
inferred_decl = Parameter::Float(name, "");
411+
}
412+
else if (type_str == "time")
413+
{
414+
inferred_decl = Parameter::Time(name, "");
415+
}
416+
else if (type_str == "string")
417+
{
418+
inferred_decl = Parameter::String(name, "");
419+
}
420+
else if (type_str == "bit_value" || type_str == "std_logic")
421+
{
422+
// Old format: single char ('0', '1', 'X', ...); new: "0b0", "0bX"
423+
const bool already_prefixed = raw_value.size() >= 3 && raw_value[0] == '0'
424+
&& (raw_value[1] == 'b' || raw_value[1] == 'B');
425+
const char bit_char = already_prefixed ? raw_value[2]
426+
: (raw_value.empty() ? '\0' : raw_value[0]);
427+
if (bit_char == '\0')
428+
{
429+
log_warning("netlist_persistent", "skipping migration of generic data entry '{}': empty value", name);
430+
skip = true;
431+
}
432+
else
433+
{
434+
migrated_value = std::string("0b") + bit_char;
435+
inferred_decl = (bit_char == '0' || bit_char == '1')
436+
? Parameter::BitVector(name, 1, "")
437+
: Parameter::LogicVector(name, 1, "");
438+
}
439+
}
440+
else if (type_str == "bit_vector" || type_str == "std_logic_vector")
441+
{
442+
const bool has_prefix = raw_value.size() >= 3 && raw_value[0] == '0'
443+
&& (raw_value[1] == 'b' || raw_value[1] == 'B'
444+
|| raw_value[1] == 'o' || raw_value[1] == 'O'
445+
|| raw_value[1] == 'x' || raw_value[1] == 'X');
446+
if (raw_value.empty())
447+
{
448+
log_warning("netlist_persistent", "skipping migration of generic data entry '{}': empty vector value", name);
449+
skip = true;
450+
}
451+
else if (has_prefix)
452+
{
453+
const char pfx = static_cast<char>(std::tolower(static_cast<unsigned char>(raw_value[1])));
454+
const auto n = static_cast<u16>(raw_value.size() - 2);
455+
const u16 size = (pfx == 'b') ? n
456+
: (pfx == 'o') ? static_cast<u16>(n * 3)
457+
: static_cast<u16>(n * 4);
458+
if (size == 0)
459+
{
460+
log_warning("netlist_persistent", "skipping migration of generic data entry '{}': zero-size vector", name);
461+
skip = true;
462+
}
463+
else
464+
{
465+
inferred_decl = (type_str == "std_logic_vector")
466+
? Parameter::LogicVector(name, size, "")
467+
: Parameter::BitVector(name, size, "");
468+
}
469+
}
470+
else
471+
{
472+
// Old format: unprefixed uppercase hex (e.g. "CAFE")
473+
const u16 size = static_cast<u16>(raw_value.size() * 4);
474+
migrated_value = "0x" + raw_value;
475+
inferred_decl = (type_str == "std_logic_vector")
476+
? Parameter::LogicVector(name, size, "")
477+
: Parameter::BitVector(name, size, "");
478+
}
479+
}
480+
481+
if (skip)
482+
continue;
483+
484+
if (inferred_decl.is_error())
485+
{
486+
log_warning("netlist_persistent", "skipping migration of generic data entry '{}' (type '{}'): {}", name, type_str, inferred_decl.get_error().get());
487+
continue;
488+
}
489+
490+
// Gate-type declaration takes precedence if available
491+
Result<Parameter> final_decl = inferred_decl;
492+
if (gate_type != nullptr)
493+
{
494+
if (auto gt_res = gate_type->get_parameter(name); gt_res.is_ok())
495+
final_decl = gt_res;
496+
}
497+
498+
if (auto res = c->set_parameter(final_decl.get(), migrated_value); res.is_error())
499+
{
500+
log_warning("netlist_persistent", "skipping migration of generic data entry '{}': {}", name, res.get_error().get());
501+
continue;
502+
}
503+
504+
to_delete.emplace_back(std::get<0>(key_tuple), name);
505+
}
506+
507+
for (const auto& [cat, key] : to_delete)
508+
c->delete_data(cat, key);
509+
}
510+
380511
bool deserialize_gate(Netlist* nl, const rapidjson::Value& val, const std::unordered_map<std::string, hal::GateType*>& gate_types)
381512
{
382513
const u32 gate_id = val["id"].GetUint();
@@ -411,6 +542,10 @@ namespace hal
411542
return false;
412543
}
413544
}
545+
else
546+
{
547+
migrate_generic_data_to_parameters(gate, it->second);
548+
}
414549

415550
if (val.HasMember("custom_functions"))
416551
{
@@ -666,6 +801,10 @@ namespace hal
666801
return false;
667802
}
668803
}
804+
else
805+
{
806+
migrate_generic_data_to_parameters(sm);
807+
}
669808

670809
if (val.HasMember("pin_groups"))
671810
{

tests/netlist/netlist_serializer.cpp

Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212
#include "netlist_test_utils.h"
1313

1414
#include <filesystem>
15+
#include <fstream>
16+
#include <sstream>
1517

1618
namespace hal {
1719
using test_utils::MIN_GATE_ID;
@@ -164,6 +166,34 @@ namespace hal {
164166

165167
return nl;
166168
}
169+
170+
// Write a minimal old-format .hal JSON to path. gate_data_entries and
171+
// mod_data_entries are raw JSON arrays for the "data" key (pass "" to omit).
172+
// gate_param_entries is a raw JSON object for the "parameters" key (pass "" to omit).
173+
void write_old_format_hal(const std::filesystem::path& path,
174+
const std::string& gate_data_entries,
175+
const std::string& gate_param_entries,
176+
const std::string& mod_data_entries) const
177+
{
178+
std::string gate_data_section = gate_data_entries.empty() ? "" : ",\"data\":" + gate_data_entries;
179+
std::string gate_param_section = gate_param_entries.empty() ? "" : ",\"parameters\":" + gate_param_entries;
180+
std::string mod_data_section = mod_data_entries.empty() ? "" : ",\"data\":" + mod_data_entries;
181+
182+
std::ostringstream ss;
183+
ss << R"({"serialization_format_version":14,"netlist":{"gate_library":")"
184+
<< m_gl->get_path().string()
185+
<< R"(","id":1,"input_file":"","design_name":"","device_name":"",)"
186+
<< R"("gates":[{"id":1,"name":"test_gate","type":"PARAM_TEST")"
187+
<< gate_data_section << gate_param_section
188+
<< R"(}],"global_vcc":[],"global_gnd":[],)"
189+
<< R"("nets":[],"global_in":[],"global_out":[],)"
190+
<< R"("modules":[{"id":1,"name":"top","parent":0,"type":"","gates":[1])"
191+
<< mod_data_section
192+
<< R"(}]}})";
193+
194+
std::ofstream f(path.string());
195+
f << ss.str();
196+
}
167197
};
168198

169199
// /**
@@ -346,6 +376,149 @@ namespace hal {
346376
TEST_END
347377
}
348378

379+
TEST_F(NetlistSerializerTest, check_generic_data_migration)
380+
{
381+
TEST_START
382+
{
383+
// Gate migration: gate type declarations take precedence.
384+
// Old-format "bit_vector" value "CAFE" (no prefix) → "0xCAFE", BitVector(16) from gate type.
385+
// Old-format "string" value for an Enum field → Enum declaration from gate type.
386+
// Non-"generic" data entries are left untouched.
387+
auto path = test_utils::create_sandbox_path("migration_gate.hal");
388+
write_old_format_hal(path,
389+
R"([["generic","width","bit_vector","CAFE"],)"
390+
R"(["generic","mode","string","inverted"],)"
391+
R"(["attribute","info","string","keep_me"]])",
392+
"", "");
393+
394+
NO_COUT_TEST_BLOCK;
395+
auto nl = netlist_serializer::deserialize_from_file(path);
396+
ASSERT_NE(nl, nullptr);
397+
Gate* g = nl->get_gate_by_id(1);
398+
ASSERT_NE(g, nullptr);
399+
400+
// "width": gate-type's BitVector(16) declaration used, unprefixed hex prepended
401+
EXPECT_TRUE(g->has_parameter("width"));
402+
EXPECT_EQ(g->get_parameter_value("width").get(), "0xCAFE");
403+
EXPECT_EQ(g->get_parameter_declaration("width").get(),
404+
m_gl->get_gate_type_by_name("PARAM_TEST")->get_parameter("width").get());
405+
406+
// "mode": gate-type's Enum declaration used, value validated as enum member
407+
EXPECT_TRUE(g->has_parameter("mode"));
408+
EXPECT_EQ(g->get_parameter_value("mode").get(), "inverted");
409+
EXPECT_EQ(g->get_parameter_declaration("mode").get(),
410+
m_gl->get_gate_type_by_name("PARAM_TEST")->get_parameter("mode").get());
411+
412+
// "generic" data entries are deleted after successful migration
413+
EXPECT_FALSE(g->has_data("generic", "width"));
414+
EXPECT_FALSE(g->has_data("generic", "mode"));
415+
416+
// non-"generic" category data is not touched
417+
EXPECT_FALSE(g->has_parameter("info"));
418+
EXPECT_TRUE(g->has_data("attribute", "info"));
419+
}
420+
{
421+
// Module migration: inference from data-type string, covering all supported types.
422+
auto path = test_utils::create_sandbox_path("migration_module_types.hal");
423+
write_old_format_hal(path, "", "",
424+
R"([["generic","B", "boolean", "true" ],)"
425+
R"( ["generic","I", "integer", "-3" ],)"
426+
R"( ["generic","F", "floating_point", "2.5" ],)"
427+
R"( ["generic","T", "time", "10ns" ],)"
428+
R"( ["generic","S", "string", "hello"],)"
429+
R"( ["generic","V0", "bit_value", "0" ],)"
430+
R"( ["generic","V1", "bit_value", "1" ],)"
431+
R"( ["generic","VX", "bit_value", "X" ],)"
432+
R"( ["generic","VZ", "std_logic", "Z" ],)"
433+
R"( ["generic","BV", "bit_vector", "ABCD" ],)"
434+
R"( ["generic","BP", "bit_vector", "0x12" ],)"
435+
R"( ["generic","LV", "std_logic_vector", "0bXX01"],)"
436+
R"( ["other", "O", "string", "skip" ]])");
437+
438+
NO_COUT_TEST_BLOCK;
439+
auto nl = netlist_serializer::deserialize_from_file(path);
440+
ASSERT_NE(nl, nullptr);
441+
Module* top = nl->get_top_module();
442+
ASSERT_NE(top, nullptr);
443+
444+
EXPECT_EQ(top->get_parameter_declaration("B").get().get_type(), Parameter::Type::Boolean);
445+
EXPECT_EQ(top->get_parameter_value("B").get(), "true");
446+
447+
EXPECT_EQ(top->get_parameter_declaration("I").get().get_type(), Parameter::Type::Integer);
448+
EXPECT_EQ(top->get_parameter_value("I").get(), "-3");
449+
450+
EXPECT_EQ(top->get_parameter_declaration("F").get().get_type(), Parameter::Type::Float);
451+
EXPECT_EQ(top->get_parameter_value("F").get(), "2.5");
452+
453+
EXPECT_EQ(top->get_parameter_declaration("T").get().get_type(), Parameter::Type::Time);
454+
EXPECT_EQ(top->get_parameter_value("T").get(), "10ns");
455+
456+
EXPECT_EQ(top->get_parameter_declaration("S").get().get_type(), Parameter::Type::String);
457+
EXPECT_EQ(top->get_parameter_value("S").get(), "hello");
458+
459+
// "bit_value" '0'/'1' → BitVector(1) with "0b" prefix
460+
EXPECT_EQ(top->get_parameter_declaration("V0").get().get_type(), Parameter::Type::BitVector);
461+
EXPECT_EQ(top->get_parameter_declaration("V0").get().get_size(), 1u);
462+
EXPECT_EQ(top->get_parameter_value("V0").get(), "0b0");
463+
464+
EXPECT_EQ(top->get_parameter_declaration("V1").get().get_type(), Parameter::Type::BitVector);
465+
EXPECT_EQ(top->get_parameter_value("V1").get(), "0b1");
466+
467+
// "bit_value" state char → LogicVector(1)
468+
EXPECT_EQ(top->get_parameter_declaration("VX").get().get_type(), Parameter::Type::LogicVector);
469+
EXPECT_EQ(top->get_parameter_declaration("VX").get().get_size(), 1u);
470+
EXPECT_EQ(top->get_parameter_value("VX").get(), "0bX");
471+
472+
// "std_logic" → LogicVector(1)
473+
EXPECT_EQ(top->get_parameter_declaration("VZ").get().get_type(), Parameter::Type::LogicVector);
474+
EXPECT_EQ(top->get_parameter_value("VZ").get(), "0bZ");
475+
476+
// "bit_vector" unprefixed hex "ABCD" → BitVector(16), value "0xABCD"
477+
EXPECT_EQ(top->get_parameter_declaration("BV").get().get_type(), Parameter::Type::BitVector);
478+
EXPECT_EQ(top->get_parameter_declaration("BV").get().get_size(), 16u);
479+
EXPECT_EQ(top->get_parameter_value("BV").get(), "0xABCD");
480+
481+
// "bit_vector" already-prefixed "0x12" → BitVector(8), value "0x12"
482+
EXPECT_EQ(top->get_parameter_declaration("BP").get().get_type(), Parameter::Type::BitVector);
483+
EXPECT_EQ(top->get_parameter_declaration("BP").get().get_size(), 8u);
484+
EXPECT_EQ(top->get_parameter_value("BP").get(), "0x12");
485+
486+
// "std_logic_vector" prefixed "0bXX01" → LogicVector(4), value "0bXX01"
487+
EXPECT_EQ(top->get_parameter_declaration("LV").get().get_type(), Parameter::Type::LogicVector);
488+
EXPECT_EQ(top->get_parameter_declaration("LV").get().get_size(), 4u);
489+
EXPECT_EQ(top->get_parameter_value("LV").get(), "0bXX01");
490+
491+
// non-"generic" category entry is untouched
492+
EXPECT_FALSE(top->has_parameter("O"));
493+
EXPECT_TRUE(top->has_data("other", "O"));
494+
495+
// migrated entries removed from data map
496+
for (const auto& key : {"B", "I", "F", "T", "S", "V0", "V1", "VX", "VZ", "BV", "BP", "LV"})
497+
EXPECT_FALSE(top->has_data("generic", key));
498+
}
499+
{
500+
// When a "parameters" section is present, migration is skipped.
501+
// The "generic" data entry stays in the data map.
502+
auto path = test_utils::create_sandbox_path("migration_skip.hal");
503+
write_old_format_hal(path,
504+
R"([["generic","width","bit_vector","DEAD"]])",
505+
R"({"width":{"type":"bit_vector","size":16,"default":"0xCAFE","value":"0xBEEF"}})",
506+
"");
507+
508+
NO_COUT_TEST_BLOCK;
509+
auto nl = netlist_serializer::deserialize_from_file(path);
510+
ASSERT_NE(nl, nullptr);
511+
Gate* g = nl->get_gate_by_id(1);
512+
ASSERT_NE(g, nullptr);
513+
514+
// Explicit "parameters" value is loaded
515+
EXPECT_EQ(g->get_parameter_value("width").get(), "0xBEEF");
516+
// Old "generic" data entry is left in the data map (migration was skipped)
517+
EXPECT_TRUE(g->has_data("generic", "width"));
518+
}
519+
TEST_END
520+
}
521+
349522
/**
350523
* Testing the serialization and deserialization of a netlist with invalid input
351524
*

0 commit comments

Comments
 (0)