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:
7
tests/CMakeLists.txt
Normal file
7
tests/CMakeLists.txt
Normal 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
90
tests/test_api.c
Normal 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
154
tests/test_parse.c
Normal 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;
|
||||
}
|
||||
Reference in New Issue
Block a user