libbsdl: scaffold + working BSDL parser (struct + JSON, C ABI)

Standalone LGPL-2.1 parser for BSDL (IEEE 1149.1), shared by essim and
bs_explorer. The parser is ported from the Viveris JTAG Core loader,
decoupled from jtag_core, and extended with two extractions the original
lacks:
  - PIN_MAP_STRING -> per-port physical package pin, scalar and vector;
  - TAP_SCAN_*     -> TAP signal roles (TDI/TDO/TMS/TCK/TRST).

Exposes a stable C ABI (bsdl_parse_file/buffer -> bsdl_t, bsdl_to_json)
with a dependency-free JSON serializer and a bsdl2json CLI. CMake builds a
versioned shared library with install/export rules for find_package(bsdl).

Verified against m2gl010t, xcku040 and xcku15p (100% pin mapping, correct
IDCODEs and TAP roles); api and parse regression tests pass, clean build.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-03 10:27:13 +02:00
parent c78ff82923
commit 6b56ab5c42
16 changed files with 2764 additions and 0 deletions

7
tests/CMakeLists.txt Normal file
View File

@@ -0,0 +1,7 @@
add_executable(test_api test_api.c)
target_link_libraries(test_api PRIVATE bsdl)
add_test(NAME api COMMAND test_api)
add_executable(test_parse test_parse.c)
target_link_libraries(test_parse PRIVATE bsdl)
add_test(NAME parse COMMAND test_parse)

90
tests/test_api.c Normal file
View File

@@ -0,0 +1,90 @@
/*
* libbsdl - API test: exercises the lifecycle + JSON layer.
* SPDX-License-Identifier: LGPL-2.1-or-later
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "bsdl/bsdl.h"
static int failures = 0;
#define CHECK(cond) do { \
if (!(cond)) { \
fprintf(stderr, "FAIL %s:%d: %s\n", __FILE__, __LINE__, #cond); \
failures++; \
} \
} while (0)
int main(void)
{
bsdl_opts_t opts;
bsdl_t model;
char* json;
/* version is reported and matches the compiled-in macro */
CHECK(bsdl_version() != NULL);
CHECK(strcmp(bsdl_version(), BSDL_VERSION_STRING) == 0);
/* defaults are sane */
bsdl_opts_default(&opts);
CHECK(opts.sort_pins_by_name == 0);
CHECK(opts.log == NULL);
CHECK(opts.log_user == NULL);
/* defined behaviour on bad input */
CHECK(bsdl_parse_file(NULL, &opts) == NULL);
CHECK(bsdl_parse_file("/nonexistent/does-not-exist.bsd", NULL) == NULL);
CHECK(bsdl_to_json(NULL) == NULL);
bsdl_free(NULL); /* must be NULL-safe */
bsdl_string_free(NULL); /* must be NULL-safe */
/* JSON layer over a hand-built model (independent of the parser) */
{
static bsdl_pin_t pins[1];
static bsdl_instr_t instrs[1];
memset(&model, 0, sizeof(model));
memset(pins, 0, sizeof(pins));
memset(instrs, 0, sizeof(instrs));
pins[0].name = "TCK";
pins[0].dir = BSDL_DIR_IN;
pins[0].physical_pin = "W20";
pins[0].tap_role = BSDL_TAP_TCK;
pins[0].in_bit = -1;
pins[0].out_bit = -1;
pins[0].ctrl_bit = -1;
instrs[0].name = "IDCODE";
instrs[0].opcode = "001001";
model.entity = "DEMO";
model.src_filename = "demo.bsd";
model.idcode = 0x12345678UL;
model.idcode_mask = 0xFFFFFFFFUL;
model.instruction_length = 6;
model.pins = pins;
model.pin_count = 1;
model.instructions = instrs;
model.instruction_count = 1;
json = bsdl_to_json(&model);
CHECK(json != NULL);
if (json) {
CHECK(strstr(json, "\"entity\":\"DEMO\"") != NULL);
CHECK(strstr(json, "\"physical_pin\":\"W20\"") != NULL);
CHECK(strstr(json, "\"tap_role\":\"tck\"") != NULL);
CHECK(strstr(json, "\"idcode\":\"0x12345678\"") != NULL);
CHECK(strstr(json, "\"name\":\"IDCODE\"") != NULL);
bsdl_string_free(json);
}
}
if (failures) {
fprintf(stderr, "%d check(s) failed\n", failures);
return 1;
}
printf("all API checks passed\n");
return 0;
}

154
tests/test_parse.c Normal file
View File

@@ -0,0 +1,154 @@
/*
* libbsdl - parser regression test over a synthetic in-memory BSDL.
* SPDX-License-Identifier: LGPL-2.1-or-later
*
* Exercises: entity, IDCODE, port directions, scalar + vector PIN_MAP,
* TAP_SCAN roles, boundary register, instruction opcodes.
*/
#include <stdio.h>
#include <string.h>
#include "bsdl/bsdl.h"
static int failures = 0;
#define CHECK(cond) do { \
if (!(cond)) { \
fprintf(stderr, "FAIL %s:%d: %s\n", __FILE__, __LINE__, #cond); \
failures++; \
} \
} while (0)
static const char DEMO[] =
"entity DEMO is\n"
" generic (PHYSICAL_PIN_MAP : string := \"PKG\");\n"
" port (\n"
" TCK:in bit;\n"
" TDI:in bit;\n"
" TDO:out bit;\n"
" TMS:in bit;\n"
" Q:out bit_vector(0 to 1);\n"
" VCC:linkage bit;\n"
" GND:linkage bit_vector(0 to 1)\n"
" );\n"
" use STD_1149_1_2001.all;\n"
" attribute PIN_MAP of DEMO : entity is PHYSICAL_PIN_MAP;\n"
" constant PKG : PIN_MAP_STRING :=\n"
" \"TCK:1,\" &\n"
" \"TDI:2,\" &\n"
" \"TDO:3,\" &\n"
" \"TMS:4,\" &\n"
" \"Q:(5,6),\" &\n"
" \"VCC:7,\" &\n"
" \"GND:(8,9)\";\n"
" attribute TAP_SCAN_IN of TDI : signal is true;\n"
" attribute TAP_SCAN_MODE of TMS : signal is true;\n"
" attribute TAP_SCAN_OUT of TDO : signal is true;\n"
" attribute TAP_SCAN_CLOCK of TCK : signal is (10.0e6, BOTH);\n"
" attribute INSTRUCTION_LENGTH of DEMO : entity is 4;\n"
" attribute INSTRUCTION_OPCODE of DEMO : entity is\n"
" \"BYPASS (1111),\" &\n"
" \"EXTEST (0000),\" &\n"
" \"SAMPLE (0001),\" &\n"
" \"IDCODE (1110)\";\n"
" attribute IDCODE_REGISTER of DEMO : entity is\n"
" \"0001\" &\n"
" \"0010001101000101\" &\n"
" \"00000000111\" &\n"
" \"1\";\n"
" attribute BOUNDARY_LENGTH of DEMO : entity is 3;\n"
" attribute BOUNDARY_REGISTER of DEMO : entity is\n"
" \"0 (BC_1, Q(0), output3, X, 2, 1, Z),\" &\n"
" \"1 (BC_1, Q(1), output3, X, 2, 1, Z),\" &\n"
" \"2 (BC_1, *, control, 1)\";\n"
"end DEMO;\n";
static const bsdl_pin_t *pin(const bsdl_t *m, const char *name)
{
size_t i;
for (i = 0; i < m->pin_count; i++)
if (!strcmp(m->pins[i].name, name))
return &m->pins[i];
return NULL;
}
static int has_instr(const bsdl_t *m, const char *name, const char *opcode)
{
size_t i;
for (i = 0; i < m->instruction_count; i++)
if (!strcmp(m->instructions[i].name, name) &&
!strcmp(m->instructions[i].opcode, opcode))
return 1;
return 0;
}
int main(void)
{
bsdl_opts_t opts;
bsdl_t *m;
const bsdl_pin_t *p;
bsdl_opts_default(&opts);
m = bsdl_parse_buffer(DEMO, sizeof(DEMO) - 1, "demo.bsd", &opts);
CHECK(m != NULL);
if (!m) {
fprintf(stderr, "parse returned NULL\n");
return 1;
}
/* header */
CHECK(strcmp(m->entity, "DEMO") == 0);
CHECK(m->idcode == 0x1234500FUL);
CHECK(m->idcode_mask == 0xFFFFFFFFUL);
CHECK(m->instruction_length == 4);
/* 4 scalars + Q(0..1) + VCC + GND(0..1) = 9 */
CHECK(m->pin_count == 9);
/* scalar pin + TAP role + physical pin */
p = pin(m, "TCK");
CHECK(p && p->dir == BSDL_DIR_IN && p->tap_role == BSDL_TAP_TCK &&
p->physical_pin && !strcmp(p->physical_pin, "1"));
p = pin(m, "TDI");
CHECK(p && p->tap_role == BSDL_TAP_TDI && !strcmp(p->physical_pin, "2"));
p = pin(m, "TDO");
CHECK(p && p->dir == BSDL_DIR_OUT && p->tap_role == BSDL_TAP_TDO);
p = pin(m, "TMS");
CHECK(p && p->tap_role == BSDL_TAP_TMS);
/* vector PIN_MAP, positional: Q(0)->5, Q(1)->6 */
p = pin(m, "Q(0)");
CHECK(p && p->dir == BSDL_DIR_OUT && p->physical_pin && !strcmp(p->physical_pin, "5"));
p = pin(m, "Q(1)");
CHECK(p && p->physical_pin && !strcmp(p->physical_pin, "6"));
/* linkage scalar + vector */
p = pin(m, "VCC");
CHECK(p && p->dir == BSDL_DIR_LINKAGE && !strcmp(p->physical_pin, "7"));
p = pin(m, "GND(0)");
CHECK(p && p->dir == BSDL_DIR_LINKAGE && !strcmp(p->physical_pin, "8"));
p = pin(m, "GND(1)");
CHECK(p && !strcmp(p->physical_pin, "9"));
/* boundary register linkage to pins (tristate output cell) */
CHECK(m->chain_count == 3);
p = pin(m, "Q(0)");
CHECK(p && p->out_bit == 0 && p->ctrl_bit == 2);
p = pin(m, "Q(1)");
CHECK(p && p->out_bit == 1 && p->ctrl_bit == 2);
/* instructions */
CHECK(has_instr(m, "IDCODE", "1110"));
CHECK(has_instr(m, "EXTEST", "0000"));
CHECK(has_instr(m, "BYPASS", "1111"));
CHECK(has_instr(m, "SAMPLE", "0001"));
bsdl_free(m);
if (failures) {
fprintf(stderr, "%d check(s) failed\n", failures);
return 1;
}
printf("all parse checks passed\n");
return 0;
}