Merge branch 'master' of github.com:steveicarus/iverilog
This commit is contained in:
commit
8e515a9a64
30
elab_expr.cc
30
elab_expr.cc
|
|
@ -1675,7 +1675,7 @@ static NetExpr* check_for_struct_members(const LineInfo*li,
|
||||||
}
|
}
|
||||||
|
|
||||||
static NetExpr* check_for_class_property(const LineInfo*li,
|
static NetExpr* check_for_class_property(const LineInfo*li,
|
||||||
Design*des, NetScope*scope,
|
Design*des, NetScope*,
|
||||||
NetNet*net,
|
NetNet*net,
|
||||||
const name_component_t&comp)
|
const name_component_t&comp)
|
||||||
{
|
{
|
||||||
|
|
@ -1885,6 +1885,28 @@ NetExpr* PECallFunction::elaborate_expr_method_(Design*des, NetScope*scope,
|
||||||
sys_expr->parm(0, new NetESignal(net));
|
sys_expr->parm(0, new NetESignal(net));
|
||||||
return sys_expr;
|
return sys_expr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (method_name == "substr") {
|
||||||
|
NetESFunc*sys_expr = new NetESFunc("$ivl_string_method$substr",
|
||||||
|
IVL_VT_STRING, 1, 3);
|
||||||
|
sys_expr->set_line(*this);
|
||||||
|
|
||||||
|
// First argument is the source string.
|
||||||
|
sys_expr->parm(0, new NetESignal(net));
|
||||||
|
|
||||||
|
ivl_assert(*this, parms_.size() == 2);
|
||||||
|
NetExpr*tmp;
|
||||||
|
|
||||||
|
tmp = elaborate_rval_expr(des, scope, IVL_VT_BOOL,
|
||||||
|
32, parms_[0], false);
|
||||||
|
sys_expr->parm(1, tmp);
|
||||||
|
|
||||||
|
tmp = elaborate_rval_expr(des, scope, IVL_VT_BOOL,
|
||||||
|
32, parms_[1], false);
|
||||||
|
sys_expr->parm(2, tmp);
|
||||||
|
|
||||||
|
return sys_expr;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (netenum_t*netenum = net->enumeration()) {
|
if (netenum_t*netenum = net->enumeration()) {
|
||||||
|
|
@ -2544,9 +2566,9 @@ NetExpr* PEIdent::elaborate_expr(Design*des, NetScope*scope,
|
||||||
NetEvent* eve = 0;
|
NetEvent* eve = 0;
|
||||||
const NetExpr*ex1, *ex2;
|
const NetExpr*ex1, *ex2;
|
||||||
|
|
||||||
NetScope*found_in = symbol_search(this, des, scope, path_,
|
/* NetScope*found_in = */ symbol_search(this, des, scope, path_,
|
||||||
net, par, eve,
|
net, par, eve,
|
||||||
ex1, ex2);
|
ex1, ex2);
|
||||||
|
|
||||||
if (net == 0) {
|
if (net == 0) {
|
||||||
cerr << get_fileline() << ": internal error: "
|
cerr << get_fileline() << ": internal error: "
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
# include "vvp_priv.h"
|
# include "vvp_priv.h"
|
||||||
|
# include <string.h>
|
||||||
# include <assert.h>
|
# include <assert.h>
|
||||||
|
|
||||||
static void fallback_eval(ivl_expr_t expr)
|
static void fallback_eval(ivl_expr_t expr)
|
||||||
|
|
@ -63,12 +64,25 @@ static void string_ex_signal(ivl_expr_t expr)
|
||||||
{
|
{
|
||||||
ivl_signal_t sig = ivl_expr_signal(expr);
|
ivl_signal_t sig = ivl_expr_signal(expr);
|
||||||
|
|
||||||
if (ivl_signal_data_type(sig) == IVL_VT_STRING) {
|
if (ivl_signal_data_type(sig) != IVL_VT_STRING) {
|
||||||
|
fallback_eval(expr);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Simple case: This is a simple variable. Generate a load
|
||||||
|
statement to load the string into the stack. */
|
||||||
|
if (ivl_signal_dimensions(sig) == 0) {
|
||||||
fprintf(vvp_out, " %%load/str v%p_0;\n", sig);
|
fprintf(vvp_out, " %%load/str v%p_0;\n", sig);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
fallback_eval(expr);
|
/* There is a word select expression, so load the index into a
|
||||||
|
register and load from the array. */
|
||||||
|
ivl_expr_t word_ex = ivl_expr_oper1(expr);
|
||||||
|
int word_ix = allocate_word();
|
||||||
|
draw_eval_expr_into_integer(word_ex, word_ix);
|
||||||
|
fprintf(vvp_out, " %%load/stra v%p, %d;\n", sig, word_ix);
|
||||||
|
clr_word(word_ix);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void string_ex_select(ivl_expr_t expr)
|
static void string_ex_select(ivl_expr_t expr)
|
||||||
|
|
@ -86,6 +100,30 @@ static void string_ex_select(ivl_expr_t expr)
|
||||||
fprintf(vvp_out, " %%load/dar/str v%p_0;\n", sig);
|
fprintf(vvp_out, " %%load/dar/str v%p_0;\n", sig);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void string_ex_substr(ivl_expr_t expr)
|
||||||
|
{
|
||||||
|
ivl_expr_t arg;
|
||||||
|
unsigned arg1;
|
||||||
|
unsigned arg2;
|
||||||
|
assert(ivl_expr_parms(expr) == 3);
|
||||||
|
|
||||||
|
arg = ivl_expr_parm(expr,0);
|
||||||
|
draw_eval_string(arg);
|
||||||
|
|
||||||
|
/* Evaluate the arguments... */
|
||||||
|
arg = ivl_expr_parm(expr, 1);
|
||||||
|
arg1 = allocate_word();
|
||||||
|
draw_eval_expr_into_integer(arg, arg1);
|
||||||
|
|
||||||
|
arg = ivl_expr_parm(expr, 2);
|
||||||
|
arg2 = allocate_word();
|
||||||
|
draw_eval_expr_into_integer(arg, arg2);
|
||||||
|
|
||||||
|
fprintf(vvp_out, " %%substr %u, %u;\n", arg1, arg2);
|
||||||
|
clr_word(arg1);
|
||||||
|
clr_word(arg2);
|
||||||
|
}
|
||||||
|
|
||||||
void draw_eval_string(ivl_expr_t expr)
|
void draw_eval_string(ivl_expr_t expr)
|
||||||
{
|
{
|
||||||
|
|
||||||
|
|
@ -106,6 +144,13 @@ void draw_eval_string(ivl_expr_t expr)
|
||||||
string_ex_select(expr);
|
string_ex_select(expr);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case IVL_EX_SFUNC:
|
||||||
|
if (strcmp(ivl_expr_name(expr), "$ivl_string_method$substr") == 0)
|
||||||
|
string_ex_substr(expr);
|
||||||
|
else
|
||||||
|
fallback_eval(expr);
|
||||||
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
fallback_eval(expr);
|
fallback_eval(expr);
|
||||||
break;
|
break;
|
||||||
|
|
|
||||||
|
|
@ -730,6 +730,7 @@ static int show_stmt_assign_sig_string(ivl_statement_t net)
|
||||||
ivl_lval_t lval = ivl_stmt_lval(net, 0);
|
ivl_lval_t lval = ivl_stmt_lval(net, 0);
|
||||||
ivl_expr_t rval = ivl_stmt_rval(net);
|
ivl_expr_t rval = ivl_stmt_rval(net);
|
||||||
ivl_expr_t part = ivl_lval_part_off(lval);
|
ivl_expr_t part = ivl_lval_part_off(lval);
|
||||||
|
ivl_expr_t aidx = ivl_lval_idx(lval);
|
||||||
ivl_signal_t var= ivl_lval_sig(lval);
|
ivl_signal_t var= ivl_lval_sig(lval);
|
||||||
|
|
||||||
assert(ivl_stmt_lvals(net) == 1);
|
assert(ivl_stmt_lvals(net) == 1);
|
||||||
|
|
@ -739,12 +740,24 @@ static int show_stmt_assign_sig_string(ivl_statement_t net)
|
||||||
/* Simplest case: no mux. Evaluate the r-value as a string and
|
/* Simplest case: no mux. Evaluate the r-value as a string and
|
||||||
store the result into the variable. Note that the
|
store the result into the variable. Note that the
|
||||||
%store/str opcode pops the string result. */
|
%store/str opcode pops the string result. */
|
||||||
if (part == 0) {
|
if (part == 0 && aidx == 0) {
|
||||||
draw_eval_string(rval);
|
draw_eval_string(rval);
|
||||||
fprintf(vvp_out, " %%store/str v%p_0;\n", var);
|
fprintf(vvp_out, " %%store/str v%p_0;\n", var);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Assign to array. The l-value has an index expression
|
||||||
|
expression so we are assigning to an array word. */
|
||||||
|
if (aidx != 0) {
|
||||||
|
unsigned ix;
|
||||||
|
assert(part == 0);
|
||||||
|
draw_eval_string(rval);
|
||||||
|
draw_eval_expr_into_integer(aidx, (ix = allocate_word()));
|
||||||
|
fprintf(vvp_out, " %%store/stra v%p, %u;\n", var, ix);
|
||||||
|
clr_word(ix);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
/* Calculate the character select for the word. */
|
/* Calculate the character select for the word. */
|
||||||
int mux_word = allocate_word();
|
int mux_word = allocate_word();
|
||||||
draw_eval_expr_into_integer(part, mux_word);
|
draw_eval_expr_into_integer(part, mux_word);
|
||||||
|
|
|
||||||
|
|
@ -475,6 +475,7 @@ static void draw_reg_in_scope(ivl_signal_t sig)
|
||||||
const char *datatype_flag = ivl_signal_integer(sig) ? "/i" :
|
const char *datatype_flag = ivl_signal_integer(sig) ? "/i" :
|
||||||
ivl_signal_signed(sig)? "/s" : "";
|
ivl_signal_signed(sig)? "/s" : "";
|
||||||
const char *local_flag = local_flag_str(sig);
|
const char *local_flag = local_flag_str(sig);
|
||||||
|
int vector_dims = 1;
|
||||||
|
|
||||||
switch (ivl_signal_data_type(sig)) {
|
switch (ivl_signal_data_type(sig)) {
|
||||||
case IVL_VT_BOOL:
|
case IVL_VT_BOOL:
|
||||||
|
|
@ -485,6 +486,11 @@ static void draw_reg_in_scope(ivl_signal_t sig)
|
||||||
break;
|
break;
|
||||||
case IVL_VT_REAL:
|
case IVL_VT_REAL:
|
||||||
datatype_flag = "/real";
|
datatype_flag = "/real";
|
||||||
|
vector_dims = 0;
|
||||||
|
break;
|
||||||
|
case IVL_VT_STRING:
|
||||||
|
datatype_flag = "/str";
|
||||||
|
vector_dims = 0;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
|
|
@ -492,7 +498,20 @@ static void draw_reg_in_scope(ivl_signal_t sig)
|
||||||
|
|
||||||
/* If the reg objects are collected into an array, then first
|
/* If the reg objects are collected into an array, then first
|
||||||
write out the .array record to declare the array indices. */
|
write out the .array record to declare the array indices. */
|
||||||
if (ivl_signal_dimensions(sig) > 0) {
|
if (ivl_signal_dimensions(sig) > 0 && vector_dims==0) {
|
||||||
|
|
||||||
|
/* Some types cannot be placed in packed dimensions, so
|
||||||
|
do not include packed dimensions. */
|
||||||
|
unsigned word_count = ivl_signal_array_count(sig);
|
||||||
|
unsigned swapped = ivl_signal_array_addr_swapped(sig);
|
||||||
|
int last = ivl_signal_array_base(sig)+word_count-1;
|
||||||
|
int first = ivl_signal_array_base(sig);
|
||||||
|
fprintf(vvp_out, "v%p .array%s \"%s\", %d %d;\n",
|
||||||
|
sig, datatype_flag,
|
||||||
|
vvp_mangle_name(ivl_signal_basename(sig)),
|
||||||
|
swapped ? first: last, swapped ? last : first);
|
||||||
|
|
||||||
|
} else if (ivl_signal_dimensions(sig) > 0) {
|
||||||
unsigned word_count = ivl_signal_array_count(sig);
|
unsigned word_count = ivl_signal_array_count(sig);
|
||||||
unsigned swapped = ivl_signal_array_addr_swapped(sig);
|
unsigned swapped = ivl_signal_array_addr_swapped(sig);
|
||||||
int last = ivl_signal_array_base(sig)+word_count-1;
|
int last = ivl_signal_array_base(sig)+word_count-1;
|
||||||
|
|
|
||||||
|
|
@ -54,7 +54,7 @@ LDFLAGS = @LDFLAGS@
|
||||||
O = sys_table.o sys_convert.o sys_countdrivers.o sys_darray.o sys_deposit.o sys_display.o \
|
O = sys_table.o sys_convert.o sys_countdrivers.o sys_darray.o sys_deposit.o sys_display.o \
|
||||||
sys_fileio.o sys_finish.o sys_icarus.o sys_plusargs.o sys_queue.o \
|
sys_fileio.o sys_finish.o sys_icarus.o sys_plusargs.o sys_queue.o \
|
||||||
sys_random.o sys_random_mti.o sys_readmem.o sys_readmem_lex.o sys_scanf.o \
|
sys_random.o sys_random_mti.o sys_readmem.o sys_readmem_lex.o sys_scanf.o \
|
||||||
sys_sdf.o sys_string.o sys_time.o sys_vcd.o sys_vcdoff.o vcd_priv.o mt19937int.o \
|
sys_sdf.o sys_time.o sys_vcd.o sys_vcdoff.o vcd_priv.o mt19937int.o \
|
||||||
sys_priv.o sdf_lexor.o sdf_parse.o stringheap.o vams_simparam.o \
|
sys_priv.o sdf_lexor.o sdf_parse.o stringheap.o vams_simparam.o \
|
||||||
table_mod.o table_mod_lexor.o table_mod_parse.o
|
table_mod.o table_mod_lexor.o table_mod_parse.o
|
||||||
OPP = vcd_priv2.o
|
OPP = vcd_priv2.o
|
||||||
|
|
@ -73,7 +73,7 @@ M = sys_clog2.o v2005_math.o
|
||||||
# Object files for va_math.vpi
|
# Object files for va_math.vpi
|
||||||
V = va_math.o
|
V = va_math.o
|
||||||
|
|
||||||
V2009 = v2009_table.o v2009_enum.o
|
V2009 = v2009_table.o v2009_enum.o v2009_string.o
|
||||||
|
|
||||||
VHDL_SYS = vhdl_table.o
|
VHDL_SYS = vhdl_table.o
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -35,7 +35,6 @@ extern void sys_queue_register();
|
||||||
extern void sys_random_register();
|
extern void sys_random_register();
|
||||||
extern void sys_random_mti_register();
|
extern void sys_random_mti_register();
|
||||||
extern void sys_readmem_register();
|
extern void sys_readmem_register();
|
||||||
extern void sys_string_register();
|
|
||||||
extern void sys_scanf_register();
|
extern void sys_scanf_register();
|
||||||
extern void sys_sdf_register();
|
extern void sys_sdf_register();
|
||||||
extern void sys_time_register();
|
extern void sys_time_register();
|
||||||
|
|
@ -210,7 +209,6 @@ void (*vlog_startup_routines[])() = {
|
||||||
sys_random_mti_register,
|
sys_random_mti_register,
|
||||||
sys_readmem_register,
|
sys_readmem_register,
|
||||||
sys_scanf_register,
|
sys_scanf_register,
|
||||||
sys_string_register,
|
|
||||||
sys_time_register,
|
sys_time_register,
|
||||||
sys_lxt_or_vcd_register,
|
sys_lxt_or_vcd_register,
|
||||||
sys_sdf_register,
|
sys_sdf_register,
|
||||||
|
|
|
||||||
|
|
@ -2,4 +2,3 @@
|
||||||
# This is the system function descriptor table for the
|
# This is the system function descriptor table for the
|
||||||
# builtin (system) functions.
|
# builtin (system) functions.
|
||||||
#
|
#
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -73,7 +73,7 @@ static PLI_INT32 len_calltf(ICARUS_VPI_CONST PLI_BYTE8*name)
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void sys_string_register(void)
|
void v2009_string_register(void)
|
||||||
{
|
{
|
||||||
s_vpi_systf_data tf_data;
|
s_vpi_systf_data tf_data;
|
||||||
vpiHandle res;
|
vpiHandle res;
|
||||||
|
|
@ -24,8 +24,10 @@
|
||||||
# include <string.h>
|
# include <string.h>
|
||||||
|
|
||||||
extern void v2009_enum_register(void);
|
extern void v2009_enum_register(void);
|
||||||
|
extern void v2009_string_register(void);
|
||||||
|
|
||||||
void (*vlog_startup_routines[])() = {
|
void (*vlog_startup_routines[])() = {
|
||||||
v2009_enum_register,
|
v2009_enum_register,
|
||||||
|
v2009_string_register,
|
||||||
0
|
0
|
||||||
};
|
};
|
||||||
|
|
|
||||||
83
vvp/array.cc
83
vvp/array.cc
|
|
@ -802,23 +802,39 @@ static char*vpi_array_vthr_A_get_str(int code, vpiHandle ref)
|
||||||
}
|
}
|
||||||
|
|
||||||
// This function return true if the underlying array words are real.
|
// This function return true if the underlying array words are real.
|
||||||
static unsigned vpi_array_is_real(vvp_array_t arr)
|
static bool vpi_array_is_real(vvp_array_t arr)
|
||||||
{
|
{
|
||||||
// Check to see if this is a variable/register array.
|
// Check to see if this is a variable/register array.
|
||||||
if (arr->vals4 != 0) return 0U; // A bit based variable/register array.
|
if (arr->vals4 != 0) // A bit based variable/register array.
|
||||||
|
return false;
|
||||||
|
|
||||||
if (dynamic_cast<vvp_darray_real*> (arr->vals))
|
if (dynamic_cast<vvp_darray_real*> (arr->vals))
|
||||||
return 1U;
|
return true;
|
||||||
|
|
||||||
|
if (arr->vals != 0)
|
||||||
|
return false;
|
||||||
|
|
||||||
// This must be a net array so look at element 0 to find the type.
|
// This must be a net array so look at element 0 to find the type.
|
||||||
assert(arr->nets != 0);
|
assert(arr->nets != 0);
|
||||||
assert(arr->array_count > 0);
|
assert(arr->array_count > 0);
|
||||||
struct __vpiRealVar*rsig = dynamic_cast<__vpiRealVar*>(arr->nets[0]);
|
struct __vpiRealVar*rsig = dynamic_cast<__vpiRealVar*>(arr->nets[0]);
|
||||||
if (rsig) {
|
if (rsig) {
|
||||||
return 1U;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0U;
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool vpi_array_is_string(vvp_array_t arr)
|
||||||
|
{
|
||||||
|
// Check to see if this is a variable/register array.
|
||||||
|
if (arr->vals4 != 0) // A bit based variable/register array.
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (dynamic_cast<vvp_darray_string*> (arr->vals))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void vpi_array_vthr_A_get_value(vpiHandle ref, p_vpi_value vp)
|
static void vpi_array_vthr_A_get_value(vpiHandle ref, p_vpi_value vp)
|
||||||
|
|
@ -833,6 +849,9 @@ static void vpi_array_vthr_A_get_value(vpiHandle ref, p_vpi_value vp)
|
||||||
if (vpi_array_is_real(parent)) {
|
if (vpi_array_is_real(parent)) {
|
||||||
double tmp = array_get_word_r(parent, index);
|
double tmp = array_get_word_r(parent, index);
|
||||||
vpip_real_get_value(tmp, vp);
|
vpip_real_get_value(tmp, vp);
|
||||||
|
} else if (vpi_array_is_string(parent)) {
|
||||||
|
string tmp = array_get_word_str(parent, index);
|
||||||
|
vpip_string_get_value(tmp, vp);
|
||||||
} else {
|
} else {
|
||||||
vvp_vector4_t tmp = array_get_word(parent, index);
|
vvp_vector4_t tmp = array_get_word(parent, index);
|
||||||
unsigned width = get_array_word_size(parent);
|
unsigned width = get_array_word_size(parent);
|
||||||
|
|
@ -1025,6 +1044,18 @@ void array_set_word(vvp_array_t arr, unsigned address, double val)
|
||||||
array_word_change(arr, address);
|
array_word_change(arr, address);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void array_set_word(vvp_array_t arr, unsigned address, const string&val)
|
||||||
|
{
|
||||||
|
assert(arr->vals != 0);
|
||||||
|
assert(arr->nets == 0);
|
||||||
|
|
||||||
|
if (address >= arr->vals->get_size())
|
||||||
|
return;
|
||||||
|
|
||||||
|
arr->vals->set_word(address, val);
|
||||||
|
array_word_change(arr, address);
|
||||||
|
}
|
||||||
|
|
||||||
vvp_vector4_t array_get_word(vvp_array_t arr, unsigned address)
|
vvp_vector4_t array_get_word(vvp_array_t arr, unsigned address)
|
||||||
{
|
{
|
||||||
if (arr->vals4) {
|
if (arr->vals4) {
|
||||||
|
|
@ -1099,6 +1130,27 @@ double array_get_word_r(vvp_array_t arr, unsigned address)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
string array_get_word_str(vvp_array_t arr, unsigned address)
|
||||||
|
{
|
||||||
|
if (arr->vals) {
|
||||||
|
assert(arr->vals4 == 0);
|
||||||
|
assert(arr->nets == 0);
|
||||||
|
// In this context, address out of bounds returns 0.0
|
||||||
|
// instead of an error.
|
||||||
|
if (address >= arr->vals->get_size())
|
||||||
|
return "";
|
||||||
|
|
||||||
|
string val;
|
||||||
|
arr->vals->get_word(address, val);
|
||||||
|
return val;
|
||||||
|
}
|
||||||
|
|
||||||
|
assert(arr->nets);
|
||||||
|
// Arrays of string nets not implemented!
|
||||||
|
assert(0);
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
static vpiHandle vpip_make_array(char*label, const char*name,
|
static vpiHandle vpip_make_array(char*label, const char*name,
|
||||||
int first_addr, int last_addr,
|
int first_addr, int last_addr,
|
||||||
bool signed_flag)
|
bool signed_flag)
|
||||||
|
|
@ -1265,8 +1317,7 @@ void compile_var2_array(char*label, char*name, int last, int first,
|
||||||
delete[] name;
|
delete[] name;
|
||||||
}
|
}
|
||||||
|
|
||||||
void compile_real_array(char*label, char*name, int last, int first,
|
void compile_real_array(char*label, char*name, int last, int first)
|
||||||
int msb, int lsb)
|
|
||||||
{
|
{
|
||||||
vpiHandle obj = vpip_make_array(label, name, first, last, true);
|
vpiHandle obj = vpip_make_array(label, name, first, last, true);
|
||||||
|
|
||||||
|
|
@ -1276,8 +1327,22 @@ void compile_real_array(char*label, char*name, int last, int first,
|
||||||
arr->vals = new vvp_darray_real(arr->array_count);
|
arr->vals = new vvp_darray_real(arr->array_count);
|
||||||
arr->vals_width = 1;
|
arr->vals_width = 1;
|
||||||
|
|
||||||
/* For a real array the MSB and LSB must be zero. */
|
count_real_arrays += 1;
|
||||||
assert(msb == 0 && lsb == 0);
|
count_real_array_words += arr->array_count;
|
||||||
|
|
||||||
|
free(label);
|
||||||
|
delete[] name;
|
||||||
|
}
|
||||||
|
|
||||||
|
void compile_string_array(char*label, char*name, int last, int first)
|
||||||
|
{
|
||||||
|
vpiHandle obj = vpip_make_array(label, name, first, last, true);
|
||||||
|
|
||||||
|
struct __vpiArray*arr = dynamic_cast<__vpiArray*>(obj);
|
||||||
|
|
||||||
|
/* Make the words. */
|
||||||
|
arr->vals = new vvp_darray_string(arr->array_count);
|
||||||
|
arr->vals_width = 1;
|
||||||
|
|
||||||
count_real_arrays += 1;
|
count_real_arrays += 1;
|
||||||
count_real_array_words += arr->array_count;
|
count_real_array_words += arr->array_count;
|
||||||
|
|
|
||||||
|
|
@ -43,9 +43,12 @@ extern void array_set_word(vvp_array_t arr, unsigned idx,
|
||||||
unsigned off, vvp_vector4_t val);
|
unsigned off, vvp_vector4_t val);
|
||||||
extern void array_set_word(vvp_array_t arr, unsigned idx,
|
extern void array_set_word(vvp_array_t arr, unsigned idx,
|
||||||
double val);
|
double val);
|
||||||
|
extern void array_set_word(vvp_array_t arr, unsigned idx,
|
||||||
|
const std::string&val);
|
||||||
|
|
||||||
extern vvp_vector4_t array_get_word(vvp_array_t array, unsigned address);
|
extern vvp_vector4_t array_get_word(vvp_array_t array, unsigned address);
|
||||||
extern double array_get_word_r(vvp_array_t array, unsigned address);
|
extern double array_get_word_r(vvp_array_t array, unsigned address);
|
||||||
|
extern std::string array_get_word_str(vvp_array_t array, unsigned address);
|
||||||
|
|
||||||
/* VPI hooks */
|
/* VPI hooks */
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -132,6 +132,7 @@ extern bool of_LOAD_DAR_R(vthread_t thr, vvp_code_t code);
|
||||||
extern bool of_LOAD_DAR_STR(vthread_t thr, vvp_code_t code);
|
extern bool of_LOAD_DAR_STR(vthread_t thr, vvp_code_t code);
|
||||||
extern bool of_LOAD_OBJ(vthread_t thr, vvp_code_t code);
|
extern bool of_LOAD_OBJ(vthread_t thr, vvp_code_t code);
|
||||||
extern bool of_LOAD_STR(vthread_t thr, vvp_code_t code);
|
extern bool of_LOAD_STR(vthread_t thr, vvp_code_t code);
|
||||||
|
extern bool of_LOAD_STRA(vthread_t thr, vvp_code_t code);
|
||||||
extern bool of_LOAD_VEC(vthread_t thr, vvp_code_t code);
|
extern bool of_LOAD_VEC(vthread_t thr, vvp_code_t code);
|
||||||
extern bool of_LOAD_VP0(vthread_t thr, vvp_code_t code);
|
extern bool of_LOAD_VP0(vthread_t thr, vvp_code_t code);
|
||||||
extern bool of_LOAD_VP0_S(vthread_t thr, vvp_code_t code);
|
extern bool of_LOAD_VP0_S(vthread_t thr, vvp_code_t code);
|
||||||
|
|
@ -186,9 +187,11 @@ extern bool of_STORE_PROP_V(vthread_t thr, vvp_code_t code);
|
||||||
extern bool of_STORE_REAL(vthread_t thr, vvp_code_t code);
|
extern bool of_STORE_REAL(vthread_t thr, vvp_code_t code);
|
||||||
extern bool of_STORE_REALA(vthread_t thr, vvp_code_t code);
|
extern bool of_STORE_REALA(vthread_t thr, vvp_code_t code);
|
||||||
extern bool of_STORE_STR(vthread_t thr, vvp_code_t code);
|
extern bool of_STORE_STR(vthread_t thr, vvp_code_t code);
|
||||||
|
extern bool of_STORE_STRA(vthread_t thr, vvp_code_t code);
|
||||||
extern bool of_SUB(vthread_t thr, vvp_code_t code);
|
extern bool of_SUB(vthread_t thr, vvp_code_t code);
|
||||||
extern bool of_SUB_WR(vthread_t thr, vvp_code_t code);
|
extern bool of_SUB_WR(vthread_t thr, vvp_code_t code);
|
||||||
extern bool of_SUBI(vthread_t thr, vvp_code_t code);
|
extern bool of_SUBI(vthread_t thr, vvp_code_t code);
|
||||||
|
extern bool of_SUBSTR(vthread_t thr, vvp_code_t code);
|
||||||
extern bool of_SUBSTR_V(vthread_t thr, vvp_code_t code);
|
extern bool of_SUBSTR_V(vthread_t thr, vvp_code_t code);
|
||||||
extern bool of_TEST_NUL(vthread_t thr, vvp_code_t code);
|
extern bool of_TEST_NUL(vthread_t thr, vvp_code_t code);
|
||||||
extern bool of_VPI_CALL(vthread_t thr, vvp_code_t code);
|
extern bool of_VPI_CALL(vthread_t thr, vvp_code_t code);
|
||||||
|
|
|
||||||
|
|
@ -180,6 +180,7 @@ static const struct opcode_table_s opcode_table[] = {
|
||||||
{ "%load/obj", of_LOAD_OBJ, 1,{OA_FUNC_PTR,OA_NONE, OA_NONE} },
|
{ "%load/obj", of_LOAD_OBJ, 1,{OA_FUNC_PTR,OA_NONE, OA_NONE} },
|
||||||
{ "%load/real", of_LOAD_REAL,1,{OA_VPI_PTR, OA_NONE, OA_NONE} },
|
{ "%load/real", of_LOAD_REAL,1,{OA_VPI_PTR, OA_NONE, OA_NONE} },
|
||||||
{ "%load/str", of_LOAD_STR, 1,{OA_FUNC_PTR,OA_NONE, OA_NONE} },
|
{ "%load/str", of_LOAD_STR, 1,{OA_FUNC_PTR,OA_NONE, OA_NONE} },
|
||||||
|
{ "%load/stra", of_LOAD_STRA,2,{OA_ARR_PTR, OA_BIT1, OA_NONE} },
|
||||||
{ "%load/v", of_LOAD_VEC,3, {OA_BIT1, OA_FUNC_PTR, OA_BIT2} },
|
{ "%load/v", of_LOAD_VEC,3, {OA_BIT1, OA_FUNC_PTR, OA_BIT2} },
|
||||||
{ "%load/vp0",of_LOAD_VP0,3,{OA_BIT1, OA_FUNC_PTR, OA_BIT2} },
|
{ "%load/vp0",of_LOAD_VP0,3,{OA_BIT1, OA_FUNC_PTR, OA_BIT2} },
|
||||||
{ "%load/vp0/s",of_LOAD_VP0_S,3,{OA_BIT1, OA_FUNC_PTR, OA_BIT2} },
|
{ "%load/vp0/s",of_LOAD_VP0_S,3,{OA_BIT1, OA_FUNC_PTR, OA_BIT2} },
|
||||||
|
|
@ -233,9 +234,11 @@ static const struct opcode_table_s opcode_table[] = {
|
||||||
{ "%store/real", of_STORE_REAL, 1, {OA_FUNC_PTR,OA_NONE, OA_NONE} },
|
{ "%store/real", of_STORE_REAL, 1, {OA_FUNC_PTR,OA_NONE, OA_NONE} },
|
||||||
{ "%store/reala", of_STORE_REALA, 2, {OA_ARR_PTR, OA_BIT1, OA_NONE} },
|
{ "%store/reala", of_STORE_REALA, 2, {OA_ARR_PTR, OA_BIT1, OA_NONE} },
|
||||||
{ "%store/str", of_STORE_STR, 1, {OA_FUNC_PTR,OA_NONE, OA_NONE} },
|
{ "%store/str", of_STORE_STR, 1, {OA_FUNC_PTR,OA_NONE, OA_NONE} },
|
||||||
|
{ "%store/stra", of_STORE_STRA, 2, {OA_ARR_PTR, OA_BIT1, OA_NONE} },
|
||||||
{ "%sub", of_SUB, 3, {OA_BIT1, OA_BIT2, OA_NUMBER} },
|
{ "%sub", of_SUB, 3, {OA_BIT1, OA_BIT2, OA_NUMBER} },
|
||||||
{ "%sub/wr", of_SUB_WR, 0, {OA_NONE, OA_NONE, OA_NONE} },
|
{ "%sub/wr", of_SUB_WR, 0, {OA_NONE, OA_NONE, OA_NONE} },
|
||||||
{ "%subi", of_SUBI, 3, {OA_BIT1, OA_BIT2, OA_NUMBER} },
|
{ "%subi", of_SUBI, 3, {OA_BIT1, OA_BIT2, OA_NUMBER} },
|
||||||
|
{ "%substr", of_SUBSTR, 2,{OA_BIT1, OA_BIT2, OA_NONE} },
|
||||||
{ "%substr/v",of_SUBSTR_V,3,{OA_BIT1, OA_BIT2, OA_NUMBER} },
|
{ "%substr/v",of_SUBSTR_V,3,{OA_BIT1, OA_BIT2, OA_NUMBER} },
|
||||||
{ "%test_nul", of_TEST_NUL, 1,{OA_FUNC_PTR,OA_NONE, OA_NONE} },
|
{ "%test_nul", of_TEST_NUL, 1,{OA_FUNC_PTR,OA_NONE, OA_NONE} },
|
||||||
{ "%wait", of_WAIT, 1, {OA_FUNC_PTR, OA_NONE, OA_NONE} },
|
{ "%wait", of_WAIT, 1, {OA_FUNC_PTR, OA_NONE, OA_NONE} },
|
||||||
|
|
|
||||||
|
|
@ -348,8 +348,9 @@ extern void compile_var2_array(char*label, char*name,
|
||||||
int last, int first,
|
int last, int first,
|
||||||
int msb, int lsb, bool signed_flag);
|
int msb, int lsb, bool signed_flag);
|
||||||
extern void compile_real_array(char*label, char*name,
|
extern void compile_real_array(char*label, char*name,
|
||||||
int last, int first,
|
int last, int first);
|
||||||
int msb, int lsb);
|
extern void compile_string_array(char*label, char*name,
|
||||||
|
int last, int first);
|
||||||
extern void compile_net_array(char*label, char*name,
|
extern void compile_net_array(char*label, char*name,
|
||||||
int last, int first);
|
int last, int first);
|
||||||
extern void compile_array_alias(char*label, char*name, char*src);
|
extern void compile_array_alias(char*label, char*name, char*src);
|
||||||
|
|
|
||||||
|
|
@ -126,6 +126,7 @@ static char* strdupnew(char const *str)
|
||||||
".array/i" { return K_ARRAY_I; }
|
".array/i" { return K_ARRAY_I; }
|
||||||
".array/real" { return K_ARRAY_R; }
|
".array/real" { return K_ARRAY_R; }
|
||||||
".array/s" { return K_ARRAY_S; }
|
".array/s" { return K_ARRAY_S; }
|
||||||
|
".array/str" { return K_ARRAY_STR; }
|
||||||
".array/port" { return K_ARRAY_PORT; }
|
".array/port" { return K_ARRAY_PORT; }
|
||||||
".cast/2" { return K_CAST_2; }
|
".cast/2" { return K_CAST_2; }
|
||||||
".cast/int" { return K_CAST_INT; }
|
".cast/int" { return K_CAST_INT; }
|
||||||
|
|
|
||||||
|
|
@ -653,6 +653,7 @@ The %load/real instruction reads a real value from the vpi-like object
|
||||||
and pushes it to the top of the real value stack.
|
and pushes it to the top of the real value stack.
|
||||||
|
|
||||||
* %load/str <var-label>
|
* %load/str <var-label>
|
||||||
|
* %load/stra <array-label>, <index>
|
||||||
* %load/dar/str <var-label>
|
* %load/dar/str <var-label>
|
||||||
|
|
||||||
The %load/str instruction gets the string from the string variable and
|
The %load/str instruction gets the string from the string variable and
|
||||||
|
|
@ -1009,7 +1010,7 @@ variable given by the label.
|
||||||
See also %load/obj.
|
See also %load/obj.
|
||||||
|
|
||||||
* %store/real <var-label>
|
* %store/real <var-label>
|
||||||
* %store/reala <var-label> <index>
|
* %store/reala <var-label>, <index>
|
||||||
|
|
||||||
This pops the top of the real variable stack and write it to the
|
This pops the top of the real variable stack and write it to the
|
||||||
object variable given bu the label.
|
object variable given bu the label.
|
||||||
|
|
@ -1018,12 +1019,15 @@ The areal version is similar, but writes to a real array using the
|
||||||
index in the index register <index>
|
index in the index register <index>
|
||||||
|
|
||||||
* %store/str <var-label>
|
* %store/str <var-label>
|
||||||
|
* %store/stra <array-label>, <index>
|
||||||
* %store/dar/r <var-label>
|
* %store/dar/r <var-label>
|
||||||
* %store/dar/str <var-label>
|
* %store/dar/str <var-label>
|
||||||
|
|
||||||
The %store/str instruction pops the top of the string stack and writes
|
The %store/str instruction pops the top of the string stack and writes
|
||||||
it to the string variable.
|
it to the string variable.
|
||||||
|
|
||||||
|
The %store/stra targets an array.
|
||||||
|
|
||||||
The %store/dar/str is similar, but the target is a dynamic array of
|
The %store/dar/str is similar, but the target is a dynamic array of
|
||||||
string string. The index is taken from signed index register 3.
|
string string. The index is taken from signed index register 3.
|
||||||
|
|
||||||
|
|
@ -1053,6 +1057,11 @@ This instruction operates on real values in word registers. The right
|
||||||
value is popped, the left value is popped, the right is subtracted
|
value is popped, the left value is popped, the right is subtracted
|
||||||
from the left, and the result pushed.
|
from the left, and the result pushed.
|
||||||
|
|
||||||
|
* %substr <start>, <end>
|
||||||
|
|
||||||
|
This instruction takes the substring of the top string in the string
|
||||||
|
stack. This implements the SystemVerilog style substring. The string
|
||||||
|
stack is popped and replaced with the result.
|
||||||
|
|
||||||
* %substr/v <bit-l>, <sel>, <wid>
|
* %substr/v <bit-l>, <sel>, <wid>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -77,7 +77,7 @@ static struct __vpiModPath*modpath_dst = 0;
|
||||||
%token K_ARITH_MOD_R K_ARITH_MOD_S
|
%token K_ARITH_MOD_R K_ARITH_MOD_S
|
||||||
%token K_ARITH_MULT K_ARITH_MULT_R K_ARITH_SUB K_ARITH_SUB_R
|
%token K_ARITH_MULT K_ARITH_MULT_R K_ARITH_SUB K_ARITH_SUB_R
|
||||||
%token K_ARITH_SUM K_ARITH_SUM_R K_ARITH_POW K_ARITH_POW_R K_ARITH_POW_S
|
%token K_ARITH_SUM K_ARITH_SUM_R K_ARITH_POW K_ARITH_POW_R K_ARITH_POW_S
|
||||||
%token K_ARRAY K_ARRAY_2U K_ARRAY_2S K_ARRAY_I K_ARRAY_R K_ARRAY_S K_ARRAY_PORT
|
%token K_ARRAY K_ARRAY_2U K_ARRAY_2S K_ARRAY_I K_ARRAY_R K_ARRAY_S K_ARRAY_STR K_ARRAY_PORT
|
||||||
%token K_CAST_INT K_CAST_REAL K_CAST_REAL_S K_CAST_2
|
%token K_CAST_INT K_CAST_REAL K_CAST_REAL_S K_CAST_2
|
||||||
%token K_CLASS
|
%token K_CLASS
|
||||||
%token K_CMP_EEQ K_CMP_EQ K_CMP_EQ_R K_CMP_NEE K_CMP_NE K_CMP_NE_R
|
%token K_CMP_EEQ K_CMP_EQ K_CMP_EQ_R K_CMP_NEE K_CMP_NE K_CMP_NE_R
|
||||||
|
|
@ -221,12 +221,15 @@ statement
|
||||||
| T_LABEL K_ARRAY_I T_STRING ',' signed_t_number signed_t_number ',' signed_t_number signed_t_number ';'
|
| T_LABEL K_ARRAY_I T_STRING ',' signed_t_number signed_t_number ',' signed_t_number signed_t_number ';'
|
||||||
{ compile_var_array($1, $3, $5, $6, $8, $9, 2); }
|
{ compile_var_array($1, $3, $5, $6, $8, $9, 2); }
|
||||||
|
|
||||||
| T_LABEL K_ARRAY_R T_STRING ',' signed_t_number signed_t_number ',' signed_t_number signed_t_number ';'
|
| T_LABEL K_ARRAY_R T_STRING ',' signed_t_number signed_t_number ';'
|
||||||
{ compile_real_array($1, $3, $5, $6, $8, $9); }
|
{ compile_real_array($1, $3, $5, $6); }
|
||||||
|
|
||||||
| T_LABEL K_ARRAY_S T_STRING ',' signed_t_number signed_t_number ',' signed_t_number signed_t_number ';'
|
| T_LABEL K_ARRAY_S T_STRING ',' signed_t_number signed_t_number ',' signed_t_number signed_t_number ';'
|
||||||
{ compile_var_array($1, $3, $5, $6, $8, $9, 1); }
|
{ compile_var_array($1, $3, $5, $6, $8, $9, 1); }
|
||||||
|
|
||||||
|
| T_LABEL K_ARRAY_STR T_STRING ',' signed_t_number signed_t_number ';'
|
||||||
|
{ compile_string_array($1, $3, $5, $6); }
|
||||||
|
|
||||||
| T_LABEL K_ARRAY T_STRING ',' signed_t_number signed_t_number ';'
|
| T_LABEL K_ARRAY T_STRING ',' signed_t_number signed_t_number ';'
|
||||||
{ compile_net_array($1, $3, $5, $6); }
|
{ compile_net_array($1, $3, $5, $6); }
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -902,6 +902,32 @@ double real_from_vpi_value(s_vpi_value*vp)
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void vpip_string_get_value(const string&val, s_vpi_value*vp)
|
||||||
|
{
|
||||||
|
char *rbuf = 0;
|
||||||
|
|
||||||
|
switch (vp->format) {
|
||||||
|
default:
|
||||||
|
fprintf(stderr, "sorry: Format %d not implemented for "
|
||||||
|
"getting string values.\n", (int)vp->format);
|
||||||
|
assert(0);
|
||||||
|
|
||||||
|
case vpiSuppressVal:
|
||||||
|
break;
|
||||||
|
|
||||||
|
case vpiObjTypeVal:
|
||||||
|
// Use the following case to actually set the value!
|
||||||
|
vp->format = vpiStringVal;
|
||||||
|
|
||||||
|
case vpiStringVal:
|
||||||
|
rbuf = need_result_buf(val.size() + 1, RBUF_VAL);
|
||||||
|
strcpy(rbuf, val.c_str());
|
||||||
|
vp->value.str = rbuf;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
void vpi_get_value(vpiHandle expr, s_vpi_value*vp)
|
void vpi_get_value(vpiHandle expr, s_vpi_value*vp)
|
||||||
{
|
{
|
||||||
assert(expr);
|
assert(expr);
|
||||||
|
|
|
||||||
|
|
@ -771,6 +771,7 @@ extern void vpip_vec4_get_value(const vvp_vector4_t&word_val, unsigned width,
|
||||||
extern void vpip_vec2_get_value(const vvp_vector2_t&word_val, unsigned width,
|
extern void vpip_vec2_get_value(const vvp_vector2_t&word_val, unsigned width,
|
||||||
bool signed_flag, s_vpi_value*vp);
|
bool signed_flag, s_vpi_value*vp);
|
||||||
extern void vpip_real_get_value(double real, s_vpi_value*vp);
|
extern void vpip_real_get_value(double real, s_vpi_value*vp);
|
||||||
|
extern void vpip_string_get_value(const std::string&val, s_vpi_value*vp);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Function defined in vpi_signal.cc to manage vpi_get_* persistent
|
* Function defined in vpi_signal.cc to manage vpi_get_* persistent
|
||||||
|
|
|
||||||
|
|
@ -3444,6 +3444,23 @@ bool of_LOAD_STR(vthread_t thr, vvp_code_t cp)
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool of_LOAD_STRA(vthread_t thr, vvp_code_t cp)
|
||||||
|
{
|
||||||
|
unsigned idx = cp->bit_idx[0];
|
||||||
|
unsigned adr = thr->words[idx].w_int;
|
||||||
|
string word;
|
||||||
|
|
||||||
|
/* The result is 0.0 if the address is undefined. */
|
||||||
|
if (thr_get_bit(thr, 4) == BIT4_1) {
|
||||||
|
word = "";
|
||||||
|
} else {
|
||||||
|
word = array_get_word_str(cp->array, adr);
|
||||||
|
}
|
||||||
|
|
||||||
|
thr->push_str(word);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
/* %load/v <bit>, <label>, <wid>
|
/* %load/v <bit>, <label>, <wid>
|
||||||
*
|
*
|
||||||
* Implement the %load/v instruction. Load the vector value of the
|
* Implement the %load/v instruction. Load the vector value of the
|
||||||
|
|
@ -5096,6 +5113,20 @@ bool of_STORE_STR(vthread_t thr, vvp_code_t cp)
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* %store/stra <array-label> <index>
|
||||||
|
*/
|
||||||
|
bool of_STORE_STRA(vthread_t thr, vvp_code_t cp)
|
||||||
|
{
|
||||||
|
unsigned idx = cp->bit_idx[0];
|
||||||
|
unsigned adr = thr->words[idx].w_int;
|
||||||
|
|
||||||
|
string val = thr->pop_str();
|
||||||
|
array_set_word(cp->array, adr, val);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
bool of_SUB(vthread_t thr, vvp_code_t cp)
|
bool of_SUB(vthread_t thr, vvp_code_t cp)
|
||||||
{
|
{
|
||||||
|
|
@ -5176,6 +5207,27 @@ bool of_SUBI(vthread_t thr, vvp_code_t cp)
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* %substr <first>, <last>
|
||||||
|
* Pop a string, take the substring (SystemVerilog style), and return
|
||||||
|
* the result to the stack. This opcode actually works by editing the
|
||||||
|
* string in place.
|
||||||
|
*/
|
||||||
|
bool of_SUBSTR(vthread_t thr, vvp_code_t cp)
|
||||||
|
{
|
||||||
|
int32_t first = thr->words[cp->bit_idx[0]].w_int;
|
||||||
|
int32_t last = thr->words[cp->bit_idx[1]].w_int;
|
||||||
|
string&val = thr->peek_str(0);
|
||||||
|
|
||||||
|
if (first < 0 || last < first || last >= (int32_t)val.size()) {
|
||||||
|
val = string("");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
val = val.substr(first, last-first+1);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* %substr/v <bitl>, <index>, <wid>
|
* %substr/v <bitl>, <index>, <wid>
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue