Rearrange how memories are supported as vvp_vector4 arrays.
This commit is contained in:
parent
04604188df
commit
85286cc086
15
ivl_target.h
15
ivl_target.h
|
|
@ -19,7 +19,7 @@
|
|||
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
|
||||
*/
|
||||
#ifdef HAVE_CVS_IDENT
|
||||
#ident "$Id: ivl_target.h,v 1.143 2005/02/19 02:43:38 steve Exp $"
|
||||
#ident "$Id: ivl_target.h,v 1.144 2005/03/03 04:34:42 steve Exp $"
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
|
|
@ -576,6 +576,9 @@ extern unsigned ivl_expr_width(ivl_expr_t net);
|
|||
|
||||
/*
|
||||
* Memory.
|
||||
* Memories are declared in Verilog source as 2 dimensional arrays of
|
||||
* reg bits. This means that they are simple arrays of vectors. The
|
||||
* vectors in the array are called "words".
|
||||
*
|
||||
* ivl_memory_name (DEPRECATED)
|
||||
*
|
||||
|
|
@ -583,6 +586,13 @@ extern unsigned ivl_expr_width(ivl_expr_t net);
|
|||
* This returns the base name of the memory object. The base name
|
||||
* does not include the name of the scopes that contains the object.
|
||||
*
|
||||
* ivl_memory_root
|
||||
* The root of the memory is the value to add to a calculated
|
||||
* address to get to a canonical (0-based) address. This value is
|
||||
* used when external code wishes to access a word. All the
|
||||
* compiled references to the word within the compiled design are
|
||||
* converted to cannonical form by the compiler.
|
||||
*
|
||||
* ivl_memory_size
|
||||
* ivl_memory_width
|
||||
* These functions return the dimensions of the memory. The size is
|
||||
|
|
@ -1527,6 +1537,9 @@ _END_DECL
|
|||
|
||||
/*
|
||||
* $Log: ivl_target.h,v $
|
||||
* Revision 1.144 2005/03/03 04:34:42 steve
|
||||
* Rearrange how memories are supported as vvp_vector4 arrays.
|
||||
*
|
||||
* Revision 1.143 2005/02/19 02:43:38 steve
|
||||
* Support shifts and divide.
|
||||
*
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@
|
|||
# 59 Temple Place - Suite 330
|
||||
# Boston, MA 02111-1307, USA
|
||||
#
|
||||
#ident "$Id: Makefile.in,v 1.14 2004/12/12 18:15:06 steve Exp $"
|
||||
#ident "$Id: Makefile.in,v 1.15 2005/03/03 04:34:42 steve Exp $"
|
||||
#
|
||||
#
|
||||
SHELL = /bin/sh
|
||||
|
|
@ -52,7 +52,7 @@ dep:
|
|||
$(CC) $(CPPFLAGS) $(CFLAGS) -MD -c $< -o $*.o
|
||||
mv $*.d dep
|
||||
|
||||
O = stub.o statement.o
|
||||
O = stub.o memory.o statement.o
|
||||
|
||||
ifeq (@WIN32@,yes)
|
||||
TGTLDFLAGS=-L.. -livl
|
||||
|
|
|
|||
|
|
@ -0,0 +1,34 @@
|
|||
/*
|
||||
* Copyright (c) 2000-2005 Stephen Williams (steve@icarus.com)
|
||||
*
|
||||
* This source code is free software; you can redistribute it
|
||||
* and/or modify it in source code form under the terms of the GNU
|
||||
* General Public License as published by the Free Software
|
||||
* Foundation; either version 2 of the License, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
|
||||
*/
|
||||
#ifdef HAVE_CVS_IDENT
|
||||
#ident "$Id: memory.c,v 1.1 2005/03/03 04:34:42 steve Exp $"
|
||||
#endif
|
||||
|
||||
# include "config.h"
|
||||
# include "priv.h"
|
||||
# include <assert.h>
|
||||
|
||||
void show_memory(ivl_memory_t mem)
|
||||
{
|
||||
fprintf(out, " mem [%u] %s [%u] <root=%d>\n",
|
||||
ivl_memory_width(mem),
|
||||
ivl_memory_basename(mem),
|
||||
ivl_memory_size(mem),
|
||||
ivl_memory_root(mem));
|
||||
}
|
||||
|
|
@ -17,7 +17,7 @@
|
|||
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
|
||||
*/
|
||||
#ifdef HAVE_CVS_IDENT
|
||||
#ident "$Id: stub.c,v 1.111 2005/02/19 02:43:39 steve Exp $"
|
||||
#ident "$Id: stub.c,v 1.112 2005/03/03 04:34:42 steve Exp $"
|
||||
#endif
|
||||
|
||||
# include "config.h"
|
||||
|
|
@ -1080,12 +1080,15 @@ static int show_scope(ivl_scope_t net, void*x)
|
|||
for (idx = 0 ; idx < ivl_scope_vars(net) ; idx += 1)
|
||||
show_variable(ivl_scope_var(net, idx));
|
||||
|
||||
for (idx = 0 ; idx < ivl_scope_events(net) ; idx += 1)
|
||||
show_event(ivl_scope_event(net, idx));
|
||||
|
||||
for (idx = 0 ; idx < ivl_scope_sigs(net) ; idx += 1)
|
||||
show_signal(ivl_scope_sig(net, idx));
|
||||
|
||||
for (idx = 0 ; idx < ivl_scope_mems(net) ; idx += 1)
|
||||
show_memory(ivl_scope_mem(net, idx));
|
||||
|
||||
for (idx = 0 ; idx < ivl_scope_events(net) ; idx += 1)
|
||||
show_event(ivl_scope_event(net, idx));
|
||||
|
||||
for (idx = 0 ; idx < ivl_scope_logs(net) ; idx += 1)
|
||||
show_logic(ivl_scope_log(net, idx));
|
||||
|
||||
|
|
@ -1122,6 +1125,9 @@ int target_design(ivl_design_t des)
|
|||
|
||||
/*
|
||||
* $Log: stub.c,v $
|
||||
* Revision 1.112 2005/03/03 04:34:42 steve
|
||||
* Rearrange how memories are supported as vvp_vector4 arrays.
|
||||
*
|
||||
* Revision 1.111 2005/02/19 02:43:39 steve
|
||||
* Support shifts and divide.
|
||||
*
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@
|
|||
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
|
||||
*/
|
||||
#ifdef HAVE_CVS_IDENT
|
||||
#ident "$Id: eval_expr.c,v 1.115 2005/02/15 07:12:55 steve Exp $"
|
||||
#ident "$Id: eval_expr.c,v 1.116 2005/03/03 04:34:42 steve Exp $"
|
||||
#endif
|
||||
|
||||
# include "vvp_priv.h"
|
||||
|
|
@ -1467,11 +1467,15 @@ static struct vector_info draw_signal_expr(ivl_expr_t exp, unsigned wid)
|
|||
return res;
|
||||
}
|
||||
|
||||
/*
|
||||
* Draw code to evaluate a memory word index expression and write the
|
||||
* value into index register 3. This expression converts the run-time
|
||||
* calculated value to cannonical form that the %load/mv takes.
|
||||
*/
|
||||
void draw_memory_index_expr(ivl_memory_t mem, ivl_expr_t ae)
|
||||
{
|
||||
int root = ivl_memory_root(mem);
|
||||
unsigned width = ivl_memory_width(mem);
|
||||
width = (width+3) & ~3;
|
||||
|
||||
switch (ivl_expr_type(ae)) {
|
||||
case IVL_EX_NUMBER: {
|
||||
|
|
@ -1493,14 +1497,14 @@ void draw_memory_index_expr(ivl_memory_t mem, ivl_expr_t ae)
|
|||
unknown_flag = 1;
|
||||
break;
|
||||
}
|
||||
fprintf(vvp_out, " %%ix/load 3, %lu;\n", (v-root)*width);
|
||||
fprintf(vvp_out, " %%ix/load 3, %lu;\n", v-root);
|
||||
fprintf(vvp_out, " %%mov 4, %c, 1;\n",
|
||||
unknown_flag?'1':'0');
|
||||
break;
|
||||
}
|
||||
case IVL_EX_ULONG: {
|
||||
unsigned v = ivl_expr_uvalue(ae);
|
||||
fprintf(vvp_out, " %%ix/load 3, %u;\n", (v-root)*width);
|
||||
fprintf(vvp_out, " %%ix/load 3, %u;\n", v-root);
|
||||
fprintf(vvp_out, " %%mov 4, 0, 1;\n");
|
||||
break;
|
||||
}
|
||||
|
|
@ -1511,8 +1515,6 @@ void draw_memory_index_expr(ivl_memory_t mem, ivl_expr_t ae)
|
|||
clr_vector(addr);
|
||||
if (root>0)
|
||||
fprintf(vvp_out, " %%ix/sub 3, %u;\n", root);
|
||||
if (width>1)
|
||||
fprintf(vvp_out, " %%ix/mul 3, %u;\n", width);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
@ -1523,7 +1525,6 @@ static struct vector_info draw_memory_expr(ivl_expr_t exp, unsigned wid)
|
|||
unsigned swid = ivl_expr_width(exp);
|
||||
ivl_memory_t mem = ivl_expr_memory(exp);
|
||||
struct vector_info res;
|
||||
unsigned idx;
|
||||
|
||||
draw_memory_index_expr(mem, ivl_expr_oper1(exp));
|
||||
|
||||
|
|
@ -1533,12 +1534,8 @@ static struct vector_info draw_memory_expr(ivl_expr_t exp, unsigned wid)
|
|||
res.base = allocate_vector(wid);
|
||||
res.wid = wid;
|
||||
|
||||
for (idx = 0 ; idx < swid ; idx += 1) {
|
||||
if (idx)
|
||||
fprintf(vvp_out, " %%ix/add 3, 1;\n");
|
||||
fprintf(vvp_out, " %%load/m %u, M_%s;\n",
|
||||
res.base+idx, vvp_memory_label(mem));
|
||||
}
|
||||
fprintf(vvp_out, " %%load/mv %u, M_%s, %u;\n",
|
||||
res.base, vvp_memory_label(mem), swid);
|
||||
|
||||
/* Pad the signal value with zeros. */
|
||||
if (swid < wid)
|
||||
|
|
@ -2130,6 +2127,9 @@ struct vector_info draw_eval_expr(ivl_expr_t exp, int stuff_ok_flag)
|
|||
|
||||
/*
|
||||
* $Log: eval_expr.c,v $
|
||||
* Revision 1.116 2005/03/03 04:34:42 steve
|
||||
* Rearrange how memories are supported as vvp_vector4 arrays.
|
||||
*
|
||||
* Revision 1.115 2005/02/15 07:12:55 steve
|
||||
* Support constant part select writes to l-values, and large part select reads from signals.
|
||||
*
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@
|
|||
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
|
||||
*/
|
||||
#ifdef HAVE_CVS_IDENT
|
||||
#ident "$Id: vvp_process.c,v 1.101 2005/02/15 07:12:55 steve Exp $"
|
||||
#ident "$Id: vvp_process.c,v 1.102 2005/03/03 04:34:42 steve Exp $"
|
||||
#endif
|
||||
|
||||
# include "vvp_priv.h"
|
||||
|
|
@ -29,6 +29,7 @@
|
|||
# include <stdlib.h>
|
||||
|
||||
static int show_statement(ivl_statement_t net, ivl_scope_t sscope);
|
||||
static void draw_eval_expr_into_integer(ivl_expr_t expr, unsigned ix);
|
||||
|
||||
unsigned local_count = 0;
|
||||
unsigned thread_count = 0;
|
||||
|
|
@ -71,14 +72,12 @@ unsigned bitchar_to_idx(char bit)
|
|||
* nexus.
|
||||
*/
|
||||
|
||||
static void set_to_lvariable(ivl_lval_t lval, unsigned idx,
|
||||
static void set_to_lvariable(ivl_lval_t lval,
|
||||
unsigned bit, unsigned wid)
|
||||
{
|
||||
ivl_signal_t sig = ivl_lval_sig(lval);
|
||||
unsigned part_off = ivl_lval_part_off(lval);
|
||||
|
||||
assert(idx == 0);
|
||||
|
||||
if (ivl_lval_mux(lval)) {
|
||||
unsigned skip_set = transient_id++;
|
||||
|
||||
|
|
@ -112,6 +111,8 @@ static void set_to_lvariable(ivl_lval_t lval, unsigned idx,
|
|||
}
|
||||
}
|
||||
|
||||
#if 0
|
||||
/* OBSOLETE */
|
||||
static void set_to_memory(ivl_memory_t mem, unsigned idx, unsigned bit)
|
||||
{
|
||||
if (idx)
|
||||
|
|
@ -119,6 +120,20 @@ static void set_to_memory(ivl_memory_t mem, unsigned idx, unsigned bit)
|
|||
fprintf(vvp_out, " %%set/m M_%s, %u;\n",
|
||||
vvp_memory_label(mem), bit);
|
||||
}
|
||||
#endif
|
||||
|
||||
/*
|
||||
* This function writes the code to set a vector to a memory word. The
|
||||
* idx is the thread register that contains the address of the word in
|
||||
* the memory, and bit is the base of the thread vector. The wid is
|
||||
* the width of the vector to be written to the word.
|
||||
*/
|
||||
static void set_to_memory_word(ivl_memory_t mem, unsigned idx,
|
||||
unsigned bit, unsigned wid)
|
||||
{
|
||||
fprintf(vvp_out, " %%set/mv M_%s, %u, %u;\n",
|
||||
vvp_memory_label(mem), bit, wid);
|
||||
}
|
||||
|
||||
/*
|
||||
* This generates an assign to a single bit of an lvalue variable. If
|
||||
|
|
@ -173,7 +188,7 @@ static void assign_to_memory(ivl_memory_t mem, unsigned idx,
|
|||
* This function, in addition to setting the value into index 0, sets
|
||||
* bit 4 to 1 if the value is unknown.
|
||||
*/
|
||||
void draw_eval_expr_into_integer(ivl_expr_t expr, unsigned ix)
|
||||
static void draw_eval_expr_into_integer(ivl_expr_t expr, unsigned ix)
|
||||
{
|
||||
struct vector_info vec;
|
||||
int word;
|
||||
|
|
@ -226,7 +241,6 @@ static void set_vec_to_lval(ivl_statement_t net, struct vector_info res)
|
|||
for (lidx = 0 ; lidx < ivl_stmt_lvals(net) ; lidx += 1) {
|
||||
unsigned skip_set = transient_id++;
|
||||
unsigned skip_set_flag = 0;
|
||||
unsigned idx;
|
||||
unsigned bit_limit = wid - cur_rbit;
|
||||
lval = ivl_stmt_lval(net, lidx);
|
||||
|
||||
|
|
@ -246,25 +260,14 @@ static void set_vec_to_lval(ivl_statement_t net, struct vector_info res)
|
|||
bit_limit = ivl_lval_width(lval);
|
||||
|
||||
if (mem) {
|
||||
for (idx = 0 ; idx < bit_limit ; idx += 1) {
|
||||
unsigned bidx = res.base < 4
|
||||
? res.base
|
||||
: (res.base+cur_rbit);
|
||||
set_to_memory(mem, idx, bidx);
|
||||
cur_rbit += 1;
|
||||
}
|
||||
#if 0
|
||||
for (idx = bit_limit; idx < ivl_lval_pins(lval); idx += 1)
|
||||
set_to_memory(mem, idx, 0);
|
||||
#else
|
||||
assert(0);
|
||||
#endif
|
||||
set_to_memory_word(mem, 3, res.base, res.wid);
|
||||
|
||||
} else {
|
||||
|
||||
unsigned bidx = res.base < 4
|
||||
? res.base
|
||||
: (res.base+cur_rbit);
|
||||
set_to_lvariable(lval, 0, bidx, bit_limit);
|
||||
set_to_lvariable(lval, bidx, bit_limit);
|
||||
cur_rbit += bit_limit;
|
||||
}
|
||||
|
||||
|
|
@ -342,6 +345,7 @@ static int show_stmt_assign_vector(ivl_statement_t net)
|
|||
bit_limit = ivl_lval_width(lval);
|
||||
|
||||
if (mem) {
|
||||
#if 0
|
||||
for (idx = 0 ; idx < bit_limit ; idx += 1) {
|
||||
set_to_memory(mem, idx,
|
||||
bitchar_to_idx(bits[cur_rbit]));
|
||||
|
|
@ -352,7 +356,9 @@ static int show_stmt_assign_vector(ivl_statement_t net)
|
|||
for (idx = bit_limit
|
||||
; idx < ivl_lval_width(lval) ; idx += 1)
|
||||
set_to_memory(mem, idx, 0);
|
||||
|
||||
#else
|
||||
assert(0);
|
||||
#endif
|
||||
|
||||
} else {
|
||||
/* Here we have the case of a blocking
|
||||
|
|
@ -388,7 +394,7 @@ static int show_stmt_assign_vector(ivl_statement_t net)
|
|||
}
|
||||
|
||||
/* write out the value into the .var. */
|
||||
set_to_lvariable(lval, 0, vect.base, vect.wid);
|
||||
set_to_lvariable(lval, vect.base, vect.wid);
|
||||
|
||||
clr_vector(vect);
|
||||
}
|
||||
|
|
@ -1560,6 +1566,9 @@ int draw_func_definition(ivl_scope_t scope)
|
|||
|
||||
/*
|
||||
* $Log: vvp_process.c,v $
|
||||
* Revision 1.102 2005/03/03 04:34:42 steve
|
||||
* Rearrange how memories are supported as vvp_vector4 arrays.
|
||||
*
|
||||
* Revision 1.101 2005/02/15 07:12:55 steve
|
||||
* Support constant part select writes to l-values, and large part select reads from signals.
|
||||
*
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
/*
|
||||
* Copyright (c) 2001 Stephen Williams (steve@icarus.com)
|
||||
*
|
||||
* $Id: README.txt,v 1.57 2005/02/07 22:42:42 steve Exp $
|
||||
* $Id: README.txt,v 1.58 2005/03/03 04:33:10 steve Exp $
|
||||
*/
|
||||
|
||||
VVP SIMULATION ENGINE
|
||||
|
|
@ -289,25 +289,28 @@ functor pointer, though.
|
|||
|
||||
MEMORY STATEMENTS:
|
||||
|
||||
Memories are arrays of words, each word a vvp_vector4_t vector of the
|
||||
same width. The memory is cannonically addressed as a 1-dimensional
|
||||
array of words, although indices are stored with the memory for
|
||||
calculating a canonical address from a multi-dimensional address.
|
||||
|
||||
Three types of memory statement perform (1) creation of a memory, (2)
|
||||
connecting a read port to an existing memory, and (3) initializing the
|
||||
memory's contents.
|
||||
|
||||
<label> .mem "name", <msb>,<lsb>, <last>,<first> ... ;
|
||||
|
||||
The pair of numbers <msb>,<lsb> defines the data port width. The pair
|
||||
The pair of numbers <msb>,<lsb> defines the word width. The pair
|
||||
<last>,<first> defines the address range. Multiple address ranges are
|
||||
allowed for multidimensional indexing.
|
||||
allowed for multidimensional indexing. This statement creates the
|
||||
memory array and makes it available to procedural code.
|
||||
|
||||
Procedural access to the memory references the memory as single array
|
||||
of bits. For this purpose, the number of bits in a memory word is
|
||||
rounded up to the next multiple of four. That is, for an 18 bit wide
|
||||
data bus, bit 0 is the lsb of the first word, bit 20 is the lsb of the
|
||||
second word.
|
||||
of words.
|
||||
|
||||
Structural read access is implemented in terms of address and data
|
||||
ports. The addresses applied to the address port are expected to be
|
||||
withing the ranges specified, not based at zero.
|
||||
within the ranges specified, not based at zero.
|
||||
|
||||
A read port is a vector of functors that is wide enough to accept all
|
||||
provided address bits and at least as wide as the requested subset of
|
||||
|
|
@ -346,18 +349,14 @@ to a memory, but synthesis may ask for lpm_ram_d[pq] objects.
|
|||
|
||||
To initialize a memory, use:
|
||||
|
||||
.mem/init <memid>[<start>],
|
||||
val val val ...
|
||||
;
|
||||
.mem/init <memid> <start>, val , val ... ;
|
||||
|
||||
<memid> is the label of the memory. [<start>] is optional,
|
||||
identifying the bits location where the first value is loaded.
|
||||
<start> must be a multiple of four, and defaults to zero, if omitted.
|
||||
<memid> is the label of the memory, and the <start> is the start
|
||||
address (cannonical) of the first word to be initialized. The start
|
||||
address allows mustliple statements be used to initialize words of a
|
||||
memory.
|
||||
|
||||
The values are decimal or hex numbers (0x prefix), which may be
|
||||
optionally separated by comma ','. Each number in the range 0..256
|
||||
initializes four memory bits. Two bits form each byte for each memory
|
||||
bit, in the usual encoding.
|
||||
The values are one per word.
|
||||
|
||||
Procedural access to the memory employs an index register to address a
|
||||
bit location in the memory, via the commands:
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@
|
|||
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
|
||||
*/
|
||||
#ifdef HAVE_CVS_IDENT
|
||||
#ident "$Id: codes.h,v 1.69 2004/12/17 04:47:47 steve Exp $"
|
||||
#ident "$Id: codes.h,v 1.70 2005/03/03 04:33:10 steve Exp $"
|
||||
#endif
|
||||
|
||||
|
||||
|
|
@ -42,6 +42,7 @@ extern bool of_AND(vthread_t thr, vvp_code_t code);
|
|||
extern bool of_ANDR(vthread_t thr, vvp_code_t code);
|
||||
extern bool of_ASSIGN_D(vthread_t thr, vvp_code_t code);
|
||||
extern bool of_ASSIGN_MEM(vthread_t thr, vvp_code_t code);
|
||||
extern bool of_ASSIGN_MV(vthread_t thr, vvp_code_t code);
|
||||
extern bool of_ASSIGN_V0(vthread_t thr, vvp_code_t code);
|
||||
extern bool of_ASSIGN_WR(vthread_t thr, vvp_code_t code);
|
||||
extern bool of_ASSIGN_X0(vthread_t thr, vvp_code_t code);
|
||||
|
|
@ -79,6 +80,7 @@ extern bool of_JMP0XZ(vthread_t thr, vvp_code_t code);
|
|||
extern bool of_JMP1(vthread_t thr, vvp_code_t code);
|
||||
extern bool of_JOIN(vthread_t thr, vvp_code_t code);
|
||||
extern bool of_LOAD_MEM(vthread_t thr, vvp_code_t code);
|
||||
extern bool of_LOAD_MV(vthread_t thr, vvp_code_t code);
|
||||
extern bool of_LOAD_NX(vthread_t thr, vvp_code_t code);
|
||||
extern bool of_LOAD_VEC(vthread_t thr, vvp_code_t code);
|
||||
extern bool of_LOAD_WR(vthread_t thr, vvp_code_t code);
|
||||
|
|
@ -99,7 +101,7 @@ extern bool of_OR(vthread_t thr, vvp_code_t code);
|
|||
extern bool of_ORR(vthread_t thr, vvp_code_t code);
|
||||
extern bool of_RELEASE_NET(vthread_t thr, vvp_code_t code);
|
||||
extern bool of_RELEASE_REG(vthread_t thr, vvp_code_t code);
|
||||
extern bool of_SET_MEM(vthread_t thr, vvp_code_t code);
|
||||
extern bool of_SET_MV(vthread_t thr, vvp_code_t code);
|
||||
extern bool of_SET_VEC(vthread_t thr, vvp_code_t code);
|
||||
extern bool of_SET_WORDR(vthread_t thr, vvp_code_t code);
|
||||
extern bool of_SET_X0(vthread_t thr, vvp_code_t code);
|
||||
|
|
@ -169,6 +171,9 @@ extern vvp_code_t codespace_null(void);
|
|||
|
||||
/*
|
||||
* $Log: codes.h,v $
|
||||
* Revision 1.70 2005/03/03 04:33:10 steve
|
||||
* Rearrange how memories are supported as vvp_vector4 arrays.
|
||||
*
|
||||
* Revision 1.69 2004/12/17 04:47:47 steve
|
||||
* Replace single release with release/net and release/reg.
|
||||
*
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@
|
|||
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
|
||||
*/
|
||||
#ifdef HAVE_CVS_IDENT
|
||||
#ident "$Id: compile.cc,v 1.188 2005/02/19 01:32:53 steve Exp $"
|
||||
#ident "$Id: compile.cc,v 1.189 2005/03/03 04:33:10 steve Exp $"
|
||||
#endif
|
||||
|
||||
# include "arith.h"
|
||||
|
|
@ -90,6 +90,7 @@ const static struct opcode_table_s opcode_table[] = {
|
|||
{ "%and/r", of_ANDR, 3, {OA_BIT1, OA_BIT2, OA_NUMBER} },
|
||||
{ "%assign/d", of_ASSIGN_D, 3, {OA_FUNC_PTR, OA_BIT1, OA_BIT2} },
|
||||
{ "%assign/m",of_ASSIGN_MEM,3,{OA_MEM_PTR,OA_BIT1, OA_BIT2} },
|
||||
{ "%assign/mv",of_ASSIGN_MV,3,{OA_MEM_PTR,OA_BIT1, OA_BIT2} },
|
||||
{ "%assign/v0",of_ASSIGN_V0,3,{OA_FUNC_PTR,OA_BIT1, OA_BIT2} },
|
||||
{ "%assign/wr",of_ASSIGN_WR,3,{OA_VPI_PTR,OA_BIT1, OA_BIT2} },
|
||||
{ "%assign/x0",of_ASSIGN_X0,3,{OA_FUNC_PTR,OA_BIT1, OA_BIT2} },
|
||||
|
|
@ -125,6 +126,7 @@ const static struct opcode_table_s opcode_table[] = {
|
|||
{ "%jmp/1", of_JMP1, 2, {OA_CODE_PTR, OA_BIT1, OA_NONE} },
|
||||
{ "%join", of_JOIN, 0, {OA_NONE, OA_NONE, OA_NONE} },
|
||||
{ "%load/m", of_LOAD_MEM,2, {OA_BIT1, OA_MEM_PTR, OA_NONE} },
|
||||
{ "%load/mv",of_LOAD_MV,3, {OA_BIT1, OA_MEM_PTR, OA_BIT2} },
|
||||
{ "%load/nx",of_LOAD_NX,3, {OA_BIT1, OA_VPI_PTR, OA_BIT2} },
|
||||
{ "%load/v", of_LOAD_VEC,3, {OA_BIT1, OA_FUNC_PTR, OA_BIT2} },
|
||||
{ "%load/wr",of_LOAD_WR,2, {OA_BIT1, OA_VPI_PTR, OA_BIT2} },
|
||||
|
|
@ -145,7 +147,7 @@ const static struct opcode_table_s opcode_table[] = {
|
|||
{ "%or/r", of_ORR, 3, {OA_BIT1, OA_BIT2, OA_NUMBER} },
|
||||
{ "%release/net",of_RELEASE_NET,1,{OA_FUNC_PTR,OA_NONE,OA_NONE} },
|
||||
{ "%release/reg",of_RELEASE_REG,1,{OA_FUNC_PTR,OA_NONE,OA_NONE} },
|
||||
{ "%set/m", of_SET_MEM,2, {OA_MEM_PTR, OA_BIT1, OA_NONE} },
|
||||
{ "%set/mv", of_SET_MV, 3, {OA_MEM_PTR, OA_BIT1, OA_BIT2} },
|
||||
{ "%set/v", of_SET_VEC,3, {OA_FUNC_PTR, OA_BIT1, OA_BIT2} },
|
||||
{ "%set/wr", of_SET_WORDR,2,{OA_VPI_PTR, OA_BIT1, OA_NONE} },
|
||||
{ "%set/x0", of_SET_X0, 2, {OA_FUNC_PTR, OA_BIT1, OA_NONE} },
|
||||
|
|
@ -1235,18 +1237,39 @@ void compile_udp_functor(char*label, char*type,
|
|||
#endif
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Take the detailed parse items from a .mem statement and generate
|
||||
* the necessary internal structures.
|
||||
*
|
||||
* <label> .mem <name>, <msb>, <lsb>, <idxs...> ;
|
||||
*
|
||||
*/
|
||||
void compile_memory(char *label, char *name, int msb, int lsb,
|
||||
unsigned idxs, long *idx)
|
||||
unsigned narg, long *args)
|
||||
{
|
||||
vvp_memory_t mem = memory_create(label);
|
||||
memory_new(mem, name, lsb, msb, idxs, idx);
|
||||
/* Create an empty memory in the symbol table. */
|
||||
vvp_memory_t mem = memory_create(label);
|
||||
|
||||
vpiHandle obj = vpip_make_memory(mem);
|
||||
compile_vpi_symbol(label, obj);
|
||||
vpip_attach_to_current_scope(obj);
|
||||
assert( narg > 0 && narg%2 == 0 );
|
||||
|
||||
free(label);
|
||||
struct memory_address_range*ranges
|
||||
= new struct memory_address_range[narg/2];
|
||||
|
||||
for (unsigned idx = 0 ; idx < narg ; idx += 2) {
|
||||
ranges[idx/2].msb = args[idx+0];
|
||||
ranges[idx/2].lsb = args[idx+1];
|
||||
}
|
||||
|
||||
memory_configure(mem, msb, lsb, narg/2, ranges);
|
||||
|
||||
delete[]ranges;
|
||||
|
||||
vpiHandle obj = vpip_make_memory(mem, name);
|
||||
compile_vpi_symbol(label, obj);
|
||||
vpip_attach_to_current_scope(obj);
|
||||
|
||||
free(label);
|
||||
free(name);
|
||||
}
|
||||
|
||||
void compile_memory_port(char *label, char *memid,
|
||||
|
|
@ -1279,19 +1302,36 @@ void compile_memory_port(char *label, char *memid,
|
|||
#endif
|
||||
}
|
||||
|
||||
void compile_memory_init(char *memid, unsigned i, unsigned char val)
|
||||
/*
|
||||
* The parser calls this multiple times to parse a .mem/init
|
||||
* statement. The first call includes a memid label and is used to
|
||||
* select the memory and the start address. Subsequent calls contain
|
||||
* only the word value to assign.
|
||||
*/
|
||||
void compile_memory_init(char *memid, unsigned i, long val)
|
||||
{
|
||||
static vvp_memory_t mem = 0x0;
|
||||
static unsigned idx;
|
||||
if (memid)
|
||||
{
|
||||
mem = memory_find(memid);
|
||||
free(memid);
|
||||
idx = i/4;
|
||||
}
|
||||
assert(mem);
|
||||
memory_init_nibble(mem, idx, val);
|
||||
idx++;
|
||||
static vvp_memory_t current_mem = 0;
|
||||
static unsigned current_word;
|
||||
|
||||
if (memid) {
|
||||
current_mem = memory_find(memid);
|
||||
free(memid);
|
||||
current_word = i;
|
||||
return;
|
||||
}
|
||||
|
||||
assert(current_mem);
|
||||
|
||||
unsigned word_wid = memory_word_width(current_mem);
|
||||
|
||||
vvp_vector4_t val4 (word_wid);
|
||||
for (unsigned idx = 0 ; idx < word_wid ; idx += 1) {
|
||||
vvp_bit4_t bit = val & 1 ? BIT4_1 : BIT4_0;
|
||||
val4.set_bit(idx, bit);
|
||||
}
|
||||
|
||||
memory_init_word(current_mem, current_word, val4);
|
||||
current_word += 1;
|
||||
}
|
||||
|
||||
/*
|
||||
|
|
@ -1617,6 +1657,9 @@ void compile_param_string(char*label, char*name, char*str, char*value)
|
|||
|
||||
/*
|
||||
* $Log: compile.cc,v $
|
||||
* Revision 1.189 2005/03/03 04:33:10 steve
|
||||
* Rearrange how memories are supported as vvp_vector4 arrays.
|
||||
*
|
||||
* Revision 1.188 2005/02/19 01:32:53 steve
|
||||
* Implement .arith/div.
|
||||
*
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@
|
|||
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
|
||||
*/
|
||||
#ifdef HAVE_CVS_IDENT
|
||||
#ident "$Id: compile.h,v 1.62 2005/02/07 22:42:42 steve Exp $"
|
||||
#ident "$Id: compile.h,v 1.63 2005/03/03 04:33:10 steve Exp $"
|
||||
#endif
|
||||
|
||||
# include <stdio.h>
|
||||
|
|
@ -204,7 +204,7 @@ extern void compile_memory_port(char *label, char *memid,
|
|||
unsigned naddr,
|
||||
unsigned argc, struct symb_s *argv);
|
||||
|
||||
extern void compile_memory_init(char *memid, unsigned idx, unsigned char val);
|
||||
extern void compile_memory_init(char *memid, unsigned idx, long val);
|
||||
|
||||
/*
|
||||
* Compile the .ufunc statement.
|
||||
|
|
@ -297,6 +297,9 @@ extern void compile_net(char*label, char*name,
|
|||
|
||||
/*
|
||||
* $Log: compile.h,v $
|
||||
* Revision 1.63 2005/03/03 04:33:10 steve
|
||||
* Rearrange how memories are supported as vvp_vector4 arrays.
|
||||
*
|
||||
* Revision 1.62 2005/02/07 22:42:42 steve
|
||||
* Add .repeat functor and BIFIF functors.
|
||||
*
|
||||
|
|
|
|||
296
vvp/memory.cc
296
vvp/memory.cc
|
|
@ -1,4 +1,5 @@
|
|||
/*
|
||||
* Copyright (c) 2005 Stephen Williams (steve@icarus.com)
|
||||
* Copyright (c) 2000 Stephen Williams (steve@icarus.com)
|
||||
* Copyright (c) 2001 Stephan Boettcher <stephan@nevis.columbia.edu>
|
||||
*
|
||||
|
|
@ -18,7 +19,7 @@
|
|||
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
|
||||
*/
|
||||
#ifdef HAVE_CVS_IDENT
|
||||
#ident "$Id: memory.cc,v 1.22 2004/10/04 01:10:59 steve Exp $"
|
||||
#ident "$Id: memory.cc,v 1.23 2005/03/03 04:33:10 steve Exp $"
|
||||
#endif
|
||||
|
||||
#include "memory.h"
|
||||
|
|
@ -36,39 +37,26 @@ typedef struct vvp_memory_port_s *vvp_memory_port_t;
|
|||
|
||||
struct vvp_memory_s
|
||||
{
|
||||
char *name; // VPI scope.name
|
||||
// Address ranges (1 or more)
|
||||
unsigned nrange;
|
||||
struct memory_address_range*ranges;
|
||||
|
||||
// Address port properties:
|
||||
unsigned size; // total number of data words
|
||||
unsigned a_idxs; // number of address indices
|
||||
vvp_memory_index_t a_idx; // vector of address indices
|
||||
// Data port properties:
|
||||
unsigned width; // number of data bits
|
||||
|
||||
// Data port properties:
|
||||
unsigned width; // number of data bits
|
||||
unsigned fwidth; // number of bytes (4bits) per data word
|
||||
int msb, lsb; // Most/Least Significant data bit (VPI)
|
||||
int msb, lsb; // Most/Least Significant data bit (VPI)
|
||||
|
||||
vvp_memory_bits_t bits; // Array of bits
|
||||
vvp_memory_port_t addr_root; // Port list root;
|
||||
// Array of words.
|
||||
unsigned word_count;
|
||||
vvp_vector4_t*words;
|
||||
|
||||
// List of ports into this memory.
|
||||
vvp_memory_port_t port_list;
|
||||
};
|
||||
|
||||
unsigned memory_data_width(vvp_memory_t mem)
|
||||
{
|
||||
return mem->width;
|
||||
}
|
||||
|
||||
#define VVP_MEMORY_NO_ADDR ((int)0x80000000)
|
||||
|
||||
struct vvp_memory_index_s
|
||||
{
|
||||
int first; // first memory address
|
||||
unsigned size; // number of valid addresses
|
||||
|
||||
// Added to correctly support vpiLeftRange and vpiRightRange
|
||||
int left;
|
||||
int right;
|
||||
};
|
||||
|
||||
#if 0
|
||||
struct vvp_memory_port_s : public functor_s
|
||||
{
|
||||
void set(vvp_ipoint_t i, bool push, unsigned val, unsigned str);
|
||||
|
|
@ -86,47 +74,7 @@ struct vvp_memory_port_s : public functor_s
|
|||
|
||||
bool writable;
|
||||
};
|
||||
|
||||
unsigned memory_size(vvp_memory_t mem)
|
||||
{
|
||||
return mem->size;
|
||||
}
|
||||
|
||||
unsigned memory_root(vvp_memory_t mem, unsigned ix)
|
||||
{
|
||||
if (ix >= mem->a_idxs)
|
||||
return 0;
|
||||
return mem->a_idx[ix].first;
|
||||
}
|
||||
|
||||
unsigned memory_left_range(vvp_memory_t mem, unsigned ix)
|
||||
{
|
||||
if (ix >= mem->a_idxs)
|
||||
return 0;
|
||||
return mem->a_idx[ix].left;
|
||||
}
|
||||
|
||||
unsigned memory_right_range(vvp_memory_t mem, unsigned ix)
|
||||
{
|
||||
if (ix >= mem->a_idxs)
|
||||
return 0;
|
||||
return mem->a_idx[ix].right;
|
||||
}
|
||||
|
||||
unsigned memory_word_left_range(vvp_memory_t mem)
|
||||
{
|
||||
return mem->msb;
|
||||
}
|
||||
|
||||
unsigned memory_word_right_range(vvp_memory_t mem)
|
||||
{
|
||||
return mem->lsb;
|
||||
}
|
||||
|
||||
char *memory_name(vvp_memory_t mem)
|
||||
{
|
||||
return mem->name;
|
||||
}
|
||||
#endif
|
||||
|
||||
// Compilation
|
||||
|
||||
|
|
@ -143,65 +91,114 @@ vvp_memory_t memory_find(char *label)
|
|||
|
||||
vvp_memory_t memory_create(char *label)
|
||||
{
|
||||
if (!memory_table)
|
||||
memory_table = new_symbol_table();
|
||||
if (!memory_table)
|
||||
memory_table = new_symbol_table();
|
||||
|
||||
assert(!memory_find(label));
|
||||
assert(!memory_find(label));
|
||||
|
||||
vvp_memory_t mem = new struct vvp_memory_s;
|
||||
vvp_memory_t mem = new struct vvp_memory_s;
|
||||
|
||||
symbol_value_t v;
|
||||
v.ptr = mem;
|
||||
sym_set_value(memory_table, label, v);
|
||||
symbol_value_t v;
|
||||
v.ptr = mem;
|
||||
sym_set_value(memory_table, label, v);
|
||||
|
||||
return mem;
|
||||
return mem;
|
||||
}
|
||||
|
||||
void memory_new(vvp_memory_t mem, char *name, int msb, int lsb,
|
||||
unsigned idxs, long *idx)
|
||||
void memory_configure(vvp_memory_t mem,
|
||||
int msb, int lsb,
|
||||
unsigned nrange,
|
||||
const struct memory_address_range*ranges)
|
||||
{
|
||||
mem->width = msb > lsb ? msb-lsb+1 : lsb-msb+1;
|
||||
mem->msb = msb;
|
||||
mem->lsb = lsb;
|
||||
mem->fwidth = (mem->width+3)/4;
|
||||
/* Get the word width details. */
|
||||
mem->width = msb > lsb ? msb-lsb+1 : lsb-msb+1;
|
||||
mem->msb = msb;
|
||||
mem->lsb = lsb;
|
||||
|
||||
assert((idxs&1) == 0);
|
||||
mem->a_idxs = idxs/2;
|
||||
mem->a_idx = (vvp_memory_index_t)
|
||||
malloc(mem->a_idxs*sizeof(struct vvp_memory_index_s));
|
||||
assert(mem->a_idxs);
|
||||
/* Make a private copy of the memory address ranges. */
|
||||
assert(nrange > 0);
|
||||
mem->nrange = nrange;
|
||||
mem->ranges = new struct memory_address_range[nrange];
|
||||
for (unsigned idx = 0 ; idx < nrange ; idx += 1)
|
||||
mem->ranges[idx] = ranges[idx];
|
||||
|
||||
mem->size = 1;
|
||||
for (unsigned i=0; i < mem->a_idxs; i++)
|
||||
{
|
||||
vvp_memory_index_t x = mem->a_idx + i;
|
||||
int msw = *(idx++);
|
||||
int lsw = *(idx++);
|
||||
/* Scan the indices (multiplying each range) to add up the
|
||||
total number of words in this memory. */
|
||||
mem->word_count = 1;
|
||||
for (unsigned idx = 0 ; idx < mem->nrange ; idx += 1) {
|
||||
struct memory_address_range*rp = mem->ranges+idx;
|
||||
|
||||
x->left = msw;
|
||||
x->right = lsw;
|
||||
unsigned count = rp->msb > rp->lsb
|
||||
? rp->msb - rp->lsb + 1
|
||||
: rp->lsb - rp->msb + 1;
|
||||
|
||||
if (msw > lsw) {
|
||||
x->size = msw - lsw + 1;
|
||||
x->first = lsw;
|
||||
mem->word_count *= count;
|
||||
}
|
||||
else {
|
||||
x->size = lsw - msw + 1;
|
||||
x->first = msw;
|
||||
}
|
||||
mem->size *= x->size;
|
||||
}
|
||||
|
||||
mem->bits = (vvp_memory_bits_t) malloc(mem->size * mem->fwidth);
|
||||
assert(mem->bits);
|
||||
memset(mem->bits, 0xaa, mem->size * mem->fwidth);
|
||||
mem->words = new vvp_vector4_t [mem->word_count];
|
||||
assert(mem->words);
|
||||
|
||||
mem->addr_root = 0x0;
|
||||
mem->name = name;
|
||||
mem->port_list = 0;
|
||||
}
|
||||
|
||||
static void update_addr(vvp_memory_port_t addr);
|
||||
unsigned memory_word_width(vvp_memory_t mem)
|
||||
{
|
||||
return mem->width;
|
||||
}
|
||||
|
||||
unsigned memory_word_count(vvp_memory_t mem)
|
||||
{
|
||||
return mem->word_count;
|
||||
}
|
||||
|
||||
long memory_word_left_range(vvp_memory_t mem)
|
||||
{
|
||||
return mem->msb;
|
||||
}
|
||||
|
||||
long memory_word_right_range(vvp_memory_t mem)
|
||||
{
|
||||
return mem->lsb;
|
||||
}
|
||||
|
||||
long memory_left_range(vvp_memory_t mem, unsigned ix)
|
||||
{
|
||||
assert(ix < mem->nrange);
|
||||
return mem->ranges[ix].msb;
|
||||
}
|
||||
|
||||
long memory_right_range(vvp_memory_t mem, unsigned ix)
|
||||
{
|
||||
assert(ix < mem->nrange);
|
||||
return mem->ranges[ix].lsb;
|
||||
}
|
||||
|
||||
vvp_vector4_t memory_get_word(vvp_memory_t mem, unsigned addr)
|
||||
{
|
||||
// XXXX For now, assume this can't happen
|
||||
assert(addr <= mem->word_count);
|
||||
|
||||
return mem->words[addr];
|
||||
}
|
||||
|
||||
void memory_init_word(vvp_memory_t mem, unsigned addr, vvp_vector4_t val)
|
||||
{
|
||||
if (addr >= mem->word_count)
|
||||
return;
|
||||
|
||||
mem->words[addr] = val;
|
||||
}
|
||||
|
||||
void memory_set_word(vvp_memory_t mem, unsigned addr, vvp_vector4_t val)
|
||||
{
|
||||
memory_init_word(mem, addr, val);
|
||||
|
||||
if (mem->port_list)
|
||||
fprintf(stderr, "XXXX memory_set_word(%u, ...)"
|
||||
" not fully implemented\n", addr);
|
||||
}
|
||||
|
||||
#if 0
|
||||
vvp_ipoint_t memory_port_new(vvp_memory_t mem,
|
||||
unsigned nbits, unsigned bitoff,
|
||||
unsigned naddr, bool writable)
|
||||
|
|
@ -240,21 +237,23 @@ vvp_ipoint_t memory_port_new(vvp_memory_t mem,
|
|||
|
||||
return a->ix;
|
||||
}
|
||||
#endif
|
||||
|
||||
void memory_init_nibble(vvp_memory_t mem, unsigned idx, unsigned char val)
|
||||
void schedule_memory(vvp_memory_t mem, unsigned addr,
|
||||
vvp_vector4_t val, unsigned long delay)
|
||||
{
|
||||
assert(idx < mem->size*mem->fwidth);
|
||||
mem->bits[idx] = val;
|
||||
fprintf(stderr, "XXXX Forgot how to schedule memory write.\n");
|
||||
}
|
||||
|
||||
// Utilities
|
||||
|
||||
#if 0
|
||||
inline static
|
||||
vvp_memory_bits_t get_word_ix(vvp_memory_t mem, unsigned idx)
|
||||
{
|
||||
return mem->bits + idx*mem->fwidth;
|
||||
}
|
||||
|
||||
#endif
|
||||
#if 0
|
||||
inline static
|
||||
vvp_memory_bits_t get_word(vvp_memory_t mem, int addr)
|
||||
{
|
||||
|
|
@ -266,7 +265,8 @@ vvp_memory_bits_t get_word(vvp_memory_t mem, int addr)
|
|||
|
||||
return get_word_ix(mem, waddr);
|
||||
}
|
||||
|
||||
#endif
|
||||
#if 0
|
||||
inline static
|
||||
bool set_bit(vvp_memory_bits_t bits, int bit, unsigned char val)
|
||||
{
|
||||
|
|
@ -276,7 +276,8 @@ bool set_bit(vvp_memory_bits_t bits, int bit, unsigned char val)
|
|||
bits[ix] = (bits[ix] &~ (3<<ip)) | ((val&3) << ip);
|
||||
return r;
|
||||
}
|
||||
|
||||
#endif
|
||||
#if 0
|
||||
inline static
|
||||
unsigned char get_nibble(vvp_memory_bits_t bits, int bit)
|
||||
{
|
||||
|
|
@ -285,13 +286,15 @@ unsigned char get_nibble(vvp_memory_bits_t bits, int bit)
|
|||
int ix = bit/4;
|
||||
return bits[ix];
|
||||
}
|
||||
|
||||
#endif
|
||||
#if 0
|
||||
inline static
|
||||
unsigned char get_bit(vvp_memory_bits_t bits, int bit)
|
||||
{
|
||||
return (get_nibble(bits, bit) >> (2*(bit&3))) & 3;
|
||||
}
|
||||
|
||||
#endif
|
||||
#if 0
|
||||
inline static
|
||||
unsigned char functor_get_inputs(vvp_ipoint_t ip)
|
||||
{
|
||||
|
|
@ -299,14 +302,16 @@ unsigned char functor_get_inputs(vvp_ipoint_t ip)
|
|||
assert(fp);
|
||||
return fp->ival;
|
||||
}
|
||||
|
||||
#endif
|
||||
#if 0
|
||||
inline static
|
||||
unsigned char functor_get_input(vvp_ipoint_t ip)
|
||||
{
|
||||
unsigned char bits = functor_get_inputs(ip);
|
||||
return (bits >> (2*ipoint_port(ip))) & 3;
|
||||
}
|
||||
|
||||
#endif
|
||||
#if 0
|
||||
static
|
||||
bool update_addr_bit(vvp_memory_port_t addr, vvp_ipoint_t ip)
|
||||
{
|
||||
|
|
@ -330,7 +335,9 @@ bool update_addr_bit(vvp_memory_port_t addr, vvp_ipoint_t ip)
|
|||
|
||||
return addr->cur_addr != old;
|
||||
}
|
||||
#endif
|
||||
|
||||
#if 0
|
||||
static
|
||||
void update_addr(vvp_memory_port_t addr)
|
||||
{
|
||||
|
|
@ -342,7 +349,9 @@ void update_addr(vvp_memory_port_t addr)
|
|||
break;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#if 0
|
||||
inline static
|
||||
void update_data(vvp_memory_port_t data)
|
||||
{
|
||||
|
|
@ -355,7 +364,9 @@ void update_data(vvp_memory_port_t data)
|
|||
df->put_oval(out, true);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#if 0
|
||||
static
|
||||
void update_data_ports(vvp_memory_t mem, vvp_memory_bits_t bits, int bit,
|
||||
unsigned char val)
|
||||
|
|
@ -379,7 +390,9 @@ void update_data_ports(vvp_memory_t mem, vvp_memory_bits_t bits, int bit,
|
|||
a = a->next;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#if 0
|
||||
static inline
|
||||
void write_event(vvp_memory_port_t p)
|
||||
{
|
||||
|
|
@ -406,7 +419,9 @@ void write_event(vvp_memory_port_t p)
|
|||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#if 0
|
||||
void vvp_memory_port_s::set(vvp_ipoint_t i, bool, unsigned val, unsigned)
|
||||
{
|
||||
// !attention! "i" may not correspond to "this"
|
||||
|
|
@ -431,10 +446,11 @@ void vvp_memory_port_s::set(vvp_ipoint_t i, bool, unsigned val, unsigned)
|
|||
write_event(this);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
// %set/mem
|
||||
|
||||
#if 0
|
||||
void memory_set(vvp_memory_t mem, unsigned idx, unsigned char val)
|
||||
{
|
||||
if (idx/4 >= (mem->size * mem->fwidth))
|
||||
|
|
@ -448,9 +464,10 @@ void memory_set(vvp_memory_t mem, unsigned idx, unsigned char val)
|
|||
|
||||
update_data_ports(mem, get_word_ix(mem, widx), bidx, val);
|
||||
}
|
||||
#endif
|
||||
|
||||
// %load/mem
|
||||
|
||||
#if 0
|
||||
unsigned memory_get(vvp_memory_t mem, unsigned idx)
|
||||
{
|
||||
if (idx/4 >= (mem->size * mem->fwidth))
|
||||
|
|
@ -458,7 +475,7 @@ unsigned memory_get(vvp_memory_t mem, unsigned idx)
|
|||
|
||||
return get_bit(mem->bits, idx);
|
||||
}
|
||||
|
||||
#endif
|
||||
// %assign/mem event scheduling
|
||||
|
||||
struct mem_assign_s: public vvp_gen_event_s
|
||||
|
|
@ -490,41 +507,18 @@ inline static void ma_free(struct mem_assign_s* cur)
|
|||
ma_free_list = cur;
|
||||
}
|
||||
|
||||
#if 0
|
||||
static void run_mem_assign(vvp_gen_event_t obj, unsigned char val)
|
||||
{
|
||||
struct mem_assign_s *e = (struct mem_assign_s *) obj;
|
||||
memory_set(e->mem, e->idx, val);
|
||||
ma_free(e);
|
||||
}
|
||||
|
||||
void schedule_memory(vvp_memory_t mem, unsigned idx,
|
||||
unsigned char val, unsigned delay)
|
||||
{
|
||||
struct mem_assign_s *e = ma_alloc();
|
||||
e->run = run_mem_assign;
|
||||
e->mem = mem;
|
||||
e->idx = idx;
|
||||
schedule_generic(e, val, delay, false);
|
||||
}
|
||||
#endif
|
||||
|
||||
/*
|
||||
* $Log: memory.cc,v $
|
||||
* Revision 1.22 2004/10/04 01:10:59 steve
|
||||
* Clean up spurious trailing white space.
|
||||
*
|
||||
* Revision 1.21 2003/09/09 00:56:45 steve
|
||||
* Reimpelement scheduler to divide nonblocking assign queue out.
|
||||
*
|
||||
* Revision 1.20 2003/02/09 23:33:26 steve
|
||||
* Spelling fixes.
|
||||
*
|
||||
* Revision 1.19 2002/09/17 00:42:22 steve
|
||||
* Proper initialization of the memories table.
|
||||
*
|
||||
* Revision 1.18 2002/08/12 01:35:08 steve
|
||||
* conditional ident string using autoconfig.
|
||||
*
|
||||
* Revision 1.17 2002/08/11 23:47:05 steve
|
||||
* Add missing Log and Ident strings.
|
||||
* Revision 1.23 2005/03/03 04:33:10 steve
|
||||
* Rearrange how memories are supported as vvp_vector4 arrays.
|
||||
*
|
||||
*/
|
||||
|
|
|
|||
122
vvp/memory.h
122
vvp/memory.h
|
|
@ -20,75 +20,105 @@
|
|||
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
|
||||
*/
|
||||
#ifdef HAVE_CVS_IDENT
|
||||
#ident "$Id: memory.h,v 1.7 2004/10/04 01:10:59 steve Exp $"
|
||||
#ident "$Id: memory.h,v 1.8 2005/03/03 04:33:10 steve Exp $"
|
||||
#endif
|
||||
|
||||
#include "pointers.h"
|
||||
#include "functor.h"
|
||||
#include "vvp_net.h"
|
||||
|
||||
/*
|
||||
** vvp_memory_t is a memory
|
||||
** vvp_memory_bits_t are bits in a memory
|
||||
** vvp_memory_index_t is a memory index range definition
|
||||
*/
|
||||
typedef struct vvp_memory_s *vvp_memory_t;
|
||||
typedef unsigned char *vvp_memory_bits_t;
|
||||
typedef struct vvp_memory_index_s *vvp_memory_index_t;
|
||||
|
||||
void memory_new(vvp_memory_t mem, char *name, int lsb, int msb,
|
||||
unsigned idxs, long *idx);
|
||||
struct memory_address_range {
|
||||
int msb;
|
||||
int lsb;
|
||||
};
|
||||
|
||||
/*
|
||||
* Given a memory device, the memory_configure function configures it
|
||||
* by defining the dimensions of the device. It is an error to
|
||||
* redefine the dimensions of a device already configured.
|
||||
*
|
||||
* The lsb and msb define the dimensions of a word. They are in
|
||||
* Verilog form. The actual word contents are vvp_vector4_t values.
|
||||
*
|
||||
* The idx array is an array of address ranges that describe the
|
||||
* complete multi-dimensional array. In a normal Verilog array, idxs
|
||||
* is 1, and idx is a pointer to a single memory_address_range. The
|
||||
* table does not need to be persistent.
|
||||
*/
|
||||
extern void memory_configure(vvp_memory_t mem, int msb, int lsb,
|
||||
unsigned idxs,
|
||||
const struct memory_address_range *idx);
|
||||
|
||||
/*
|
||||
* init_word and set_word functions take the memory to be manipulated
|
||||
* and write a word value at the given word address. The idx is the
|
||||
* canonical (0-based, 1-dimensional) address of the word to be
|
||||
* written. The caller needs to have converted any multi-dimensional
|
||||
* address into a canonical address first.
|
||||
*
|
||||
* The difference between init_word and set_word are that the set_word
|
||||
* function causes values to be propagated through structural ports,
|
||||
* but the init_word does not.
|
||||
*/
|
||||
extern void memory_init_word(vvp_memory_t mem,
|
||||
unsigned idx,
|
||||
vvp_vector4_t val);
|
||||
extern void memory_set_word(vvp_memory_t mem,
|
||||
unsigned idx,
|
||||
vvp_vector4_t val);
|
||||
|
||||
/*
|
||||
* this doesn't actually write the value to the memory word, but
|
||||
* scedules for the write to happen some time in the future. The delay
|
||||
* is in simulation clock units
|
||||
*/
|
||||
void schedule_memory(vvp_memory_t mem, unsigned addr,
|
||||
vvp_vector4_t val, unsigned long delay);
|
||||
|
||||
/*
|
||||
* Get the word value at the given index into the memory.
|
||||
*/
|
||||
extern vvp_vector4_t memory_get_word(vvp_memory_t mem, unsigned idx);
|
||||
|
||||
#if 0
|
||||
vvp_ipoint_t memory_port_new(vvp_memory_t mem,
|
||||
unsigned nbits, unsigned bitoff,
|
||||
unsigned naddr, bool writable);
|
||||
#endif
|
||||
|
||||
void memory_init_nibble(vvp_memory_t mem, unsigned idx, unsigned char val);
|
||||
|
||||
void memory_set(vvp_memory_t mem, unsigned idx, unsigned char val);
|
||||
unsigned memory_get(vvp_memory_t mem, unsigned idx);
|
||||
void schedule_memory(vvp_memory_t mem, unsigned idx,
|
||||
unsigned char val, unsigned delay);
|
||||
|
||||
unsigned memory_size(vvp_memory_t mem);
|
||||
char *memory_name(vvp_memory_t mem);
|
||||
unsigned memory_data_width(vvp_memory_t mem);
|
||||
unsigned memory_root(vvp_memory_t mem, unsigned ix = 0);
|
||||
unsigned memory_left_range(vvp_memory_t mem, unsigned ix = 0);
|
||||
unsigned memory_right_range(vvp_memory_t mem, unsigned ix = 0);
|
||||
unsigned memory_word_left_range(vvp_memory_t mem);
|
||||
unsigned memory_word_right_range(vvp_memory_t mem);
|
||||
/* Number of words in the memory. */
|
||||
unsigned memory_word_count(vvp_memory_t mem);
|
||||
/* Width of a word */
|
||||
unsigned memory_word_width(vvp_memory_t mem);
|
||||
/* Get the user declared geometry of the memory address. This is the
|
||||
msb and lsb values for each pair in the multi-dimensional array. */
|
||||
long memory_left_range(vvp_memory_t mem, unsigned ix);
|
||||
long memory_right_range(vvp_memory_t mem, unsigned ix);
|
||||
/* Get the user defined geometry for the memory *word*. */
|
||||
long memory_word_left_range(vvp_memory_t mem);
|
||||
long memory_word_right_range(vvp_memory_t mem);
|
||||
|
||||
/*
|
||||
** Access to the memory symbol table.
|
||||
**
|
||||
** The memory_find function locates the memory device by name. If the
|
||||
** device does not exist, a nil is returned.
|
||||
**
|
||||
** The memory_create functio create a new memory device with the given
|
||||
** name. It is a fatal error to try to create a device that already exists.
|
||||
*/
|
||||
vvp_memory_t memory_find(char *label);
|
||||
vvp_memory_t memory_create(char *label);
|
||||
|
||||
/*
|
||||
* $Log: memory.h,v $
|
||||
* Revision 1.7 2004/10/04 01:10:59 steve
|
||||
* Clean up spurious trailing white space.
|
||||
*
|
||||
* Revision 1.6 2002/08/12 01:35:08 steve
|
||||
* conditional ident string using autoconfig.
|
||||
*
|
||||
* Revision 1.5 2002/01/31 04:28:17 steve
|
||||
* Full support for $readmem ranges (Tom Verbeure)
|
||||
*
|
||||
* Revision 1.4 2001/10/31 04:27:47 steve
|
||||
* Rewrite the functor type to have fewer functor modes,
|
||||
* and use objects to manage the different types.
|
||||
* (Stephan Boettcher)
|
||||
*
|
||||
* Revision 1.3 2001/06/15 03:28:31 steve
|
||||
* Change the VPI call process so that loaded .vpi modules
|
||||
* use a function table instead of implicit binding.
|
||||
*
|
||||
* Revision 1.2 2001/05/08 23:59:33 steve
|
||||
* Add ivl and vvp.tgt support for memories in
|
||||
* expressions and l-values. (Stephan Boettcher)
|
||||
*
|
||||
* Revision 1.1 2001/05/01 01:09:39 steve
|
||||
* Add support for memory objects. (Stephan Boettcher)
|
||||
* Revision 1.8 2005/03/03 04:33:10 steve
|
||||
* Rearrange how memories are supported as vvp_vector4 arrays.
|
||||
*
|
||||
*/
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
/*
|
||||
* Copyright (c) 2001-2003 Stephen Williams (steve@icarus.com)
|
||||
*
|
||||
* $Id: opcodes.txt,v 1.60 2005/02/14 01:50:23 steve Exp $
|
||||
* $Id: opcodes.txt,v 1.61 2005/03/03 04:33:10 steve Exp $
|
||||
*/
|
||||
|
||||
|
||||
|
|
@ -70,13 +70,25 @@ The %assign/d instruction is exactly the same as the %assign
|
|||
instruction, except that the <delay> specifies one of the index
|
||||
registers, that contain a calculated delay.
|
||||
|
||||
* %assign/m <memory-label>, <delay>, <bit>
|
||||
* %assign/m <memory-label>, <delay>, <bit> (OBSOLETE)
|
||||
|
||||
This instruction does a non-blocking assignment to a bit in a memory
|
||||
from the specified thread register <bit>. The memory bit is addressed
|
||||
by index register 3. Bit address zero is the LSB of the first memory
|
||||
word.
|
||||
|
||||
* %assign/mv <memory-label>, <delay>, <bit>
|
||||
|
||||
the %assign/mv instruction assigns a vector value to a word in the
|
||||
labeled memory. The <delay> is the delay in simulation time to the
|
||||
assignment (0 for non-blocking assignment) and the <bit> is the base
|
||||
of the vector to write.
|
||||
|
||||
The width of the word is retrived from index register 0.
|
||||
|
||||
The address of the word in the memory is from index register 3. The
|
||||
address is cannonical form.
|
||||
|
||||
* %assign/v0 <var-label>, <delay>, <bit>
|
||||
|
||||
The %assign/v0 instruction is a vector version of non-blocking
|
||||
|
|
@ -342,13 +354,18 @@ If the matching child instruction is still running, a %join suspends
|
|||
the calling thread until the child ends. If the child is already
|
||||
ended, then the %join does not block or yield the thread.
|
||||
|
||||
* %load/m <bit>, <memory-label>
|
||||
* %load/m <bit>, <memory-label> (OBSOLETE)
|
||||
|
||||
This instruction loads a value from a memory bit into the specified
|
||||
thread register bit. The memory bit is addressed by index register 3.
|
||||
Bit address zero is the LSB of the first memory word. This
|
||||
instruction loads only a single bit.
|
||||
|
||||
* %load/mv <bit>, <memory-label>, <wid>
|
||||
|
||||
this inctruction loads a word from the specified memory. The word
|
||||
address is in index register 3. The width should match the width of
|
||||
the memory word.
|
||||
|
||||
* %load/nx <bit>, <vpi-label>, <idx>
|
||||
|
||||
|
|
@ -508,12 +525,13 @@ addressed variable bit and working up. If the <bit> is one of the
|
|||
constant bits, then the value is repeated for the width. Otherwise,
|
||||
the vector is taken from increasing thread bit addresses.
|
||||
|
||||
* %set/m <memory-label>, <bit>
|
||||
* %set/mv <memory-label>, <bit>, <wid>
|
||||
|
||||
This instruction set a bit in a memory from the specified thread
|
||||
register bit. The memory bit is addressed by index register 3. Bit
|
||||
address zero is the LSB of the first memory word. This instruction
|
||||
sets only a single bit.
|
||||
This sets a thread vector to a memory word. The <memory-label>
|
||||
addresses a memory device, and the <bit>,<wid> describe a vector to be
|
||||
written. Index register 3 contains the address of the word within the
|
||||
memory. The address (in canonical form) is precalculated and loaded
|
||||
into index register 3.
|
||||
|
||||
|
||||
* %set/wr <vpi-label>, <bit>
|
||||
|
|
|
|||
24
vvp/parse.y
24
vvp/parse.y
|
|
@ -19,7 +19,7 @@
|
|||
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
|
||||
*/
|
||||
#ifdef HAVE_CVS_IDENT
|
||||
#ident "$Id: parse.y,v 1.66 2005/02/07 22:42:42 steve Exp $"
|
||||
#ident "$Id: parse.y,v 1.67 2005/03/03 04:33:10 steve Exp $"
|
||||
#endif
|
||||
|
||||
# include "parse_misc.h"
|
||||
|
|
@ -626,16 +626,17 @@ udp_table
|
|||
;
|
||||
|
||||
mem_init_stmt
|
||||
: K_MEM_I symbol ',' T_NUMBER o_komma
|
||||
{ compile_memory_init($2.text, $2.idx, $4); }
|
||||
| mem_init_stmt T_NUMBER o_komma
|
||||
{ compile_memory_init(0x0, 0, $2); }
|
||||
;
|
||||
: K_MEM_I symbol T_NUMBER ','
|
||||
{ compile_memory_init($2.text, $3, 0); }
|
||||
mem_init_list ';'
|
||||
;
|
||||
|
||||
o_komma
|
||||
: /* empty */
|
||||
| ','
|
||||
;
|
||||
mem_init_list
|
||||
: mem_init_list ',' T_NUMBER
|
||||
{ compile_memory_init(0, 0, $3); }
|
||||
| T_NUMBER
|
||||
{ compile_memory_init(0, 0, $1); }
|
||||
;
|
||||
|
||||
signed_t_number
|
||||
: T_NUMBER { $$ = $1; }
|
||||
|
|
@ -672,6 +673,9 @@ int compile_design(const char*path)
|
|||
|
||||
/*
|
||||
* $Log: parse.y,v $
|
||||
* Revision 1.67 2005/03/03 04:33:10 steve
|
||||
* Rearrange how memories are supported as vvp_vector4 arrays.
|
||||
*
|
||||
* Revision 1.66 2005/02/07 22:42:42 steve
|
||||
* Add .repeat functor and BIFIF functors.
|
||||
*
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
/*
|
||||
* Copyright (c) 2005 Stephen Williams (steve@icarus.com>
|
||||
* Copyright (c) 1999-2000 Picture Elements, Inc.
|
||||
* Stephen Williams (steve@picturel.com)
|
||||
* Copyright (c) 2001 Stephan Boettcher <stephan@nevis.columbia.edu>
|
||||
|
|
@ -27,7 +28,7 @@
|
|||
* Picture Elements, Inc., 777 Panoramic Way, Berkeley, CA 94704.
|
||||
*/
|
||||
#ifdef HAVE_CVS_IDENT
|
||||
#ident "$Id: vpi_memory.cc,v 1.23 2004/05/19 03:30:46 steve Exp $"
|
||||
#ident "$Id: vpi_memory.cc,v 1.24 2005/03/03 04:33:10 steve Exp $"
|
||||
#endif
|
||||
|
||||
# include "vpi_priv.h"
|
||||
|
|
@ -54,6 +55,7 @@ struct __vpiMemory {
|
|||
struct __vpiScope* scope;
|
||||
struct __vpiMemoryWord*words;
|
||||
vvp_memory_t mem;
|
||||
const char*name; /* Permanently allocated string. */
|
||||
struct __vpiDecConst left_range;
|
||||
struct __vpiDecConst right_range;
|
||||
struct __vpiDecConst word_left_range;
|
||||
|
|
@ -96,7 +98,7 @@ static int vpi_memory_get(int code, vpiHandle ref)
|
|||
|
||||
switch (code) {
|
||||
case vpiSize:
|
||||
return (int)memory_size(rfp->mem);
|
||||
return (int)memory_word_count(rfp->mem);
|
||||
|
||||
default:
|
||||
return 0;
|
||||
|
|
@ -110,17 +112,16 @@ static char* memory_get_str(int code, vpiHandle ref)
|
|||
struct __vpiMemory*rfp = (struct __vpiMemory*)ref;
|
||||
|
||||
char *bn = strdup(vpi_get_str(vpiFullName, &rfp->scope->base));
|
||||
char *nm = memory_name(rfp->mem);
|
||||
|
||||
char *rbuf = need_result_buf(strlen(bn) + strlen(nm) + 2, RBUF_STR);
|
||||
char *rbuf = need_result_buf(strlen(bn)+strlen(rfp->name)+2, RBUF_STR);
|
||||
|
||||
switch (code) {
|
||||
case vpiFullName:
|
||||
sprintf(rbuf, "%s.%s", bn, nm);
|
||||
sprintf(rbuf, "%s.%s", bn, rfp->name);
|
||||
free(bn);
|
||||
return rbuf;
|
||||
case vpiName:
|
||||
strcpy(rbuf, nm);
|
||||
strcpy(rbuf, rfp->name);
|
||||
free(bn);
|
||||
return rbuf;
|
||||
}
|
||||
|
|
@ -134,7 +135,7 @@ static vpiHandle memory_scan(vpiHandle ref, int)
|
|||
struct __vpiMemWordIterator*obj = (struct __vpiMemWordIterator*)ref;
|
||||
assert(ref->vpi_type->type_code == vpiIterator);
|
||||
|
||||
if (obj->next >= memory_size(obj->mem->mem)) {
|
||||
if (obj->next >= memory_word_count(obj->mem->mem)) {
|
||||
vpi_free_object(ref);
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -189,9 +190,10 @@ static vpiHandle memory_index(vpiHandle ref, int index)
|
|||
struct __vpiMemory*rfp = (struct __vpiMemory*)ref;
|
||||
assert(ref->vpi_type->type_code==vpiMemory);
|
||||
|
||||
index -= memory_root(rfp->mem);
|
||||
if (index >= (int)memory_size(rfp->mem)) return 0;
|
||||
if (index < 0) return 0;
|
||||
if (index >= (int)memory_word_count(rfp->mem))
|
||||
return 0;
|
||||
if (index < 0)
|
||||
return 0;
|
||||
|
||||
memory_make_word_handles(rfp);
|
||||
return &(rfp->words[index].base);
|
||||
|
|
@ -226,7 +228,7 @@ static int memory_word_get(int code, vpiHandle ref)
|
|||
|
||||
switch (code) {
|
||||
case vpiSize:
|
||||
return memory_data_width(rfp->mem->mem);
|
||||
return memory_word_width(rfp->mem->mem);
|
||||
|
||||
default:
|
||||
return 0;
|
||||
|
|
@ -241,26 +243,32 @@ static vpiHandle memory_word_put(vpiHandle ref, p_vpi_value val)
|
|||
|
||||
/* Get the width of the memory, and the byte index of the
|
||||
first byte of the word. */
|
||||
unsigned width = memory_data_width(rfp->mem->mem);
|
||||
unsigned word_offset = memory_root(rfp->mem->mem);
|
||||
unsigned bidx = (rfp->index.value - word_offset) * ((width+3)&~3);
|
||||
unsigned width = memory_word_width(rfp->mem->mem);
|
||||
unsigned word_addr = rfp->index.value;
|
||||
|
||||
/* Build up the word value from whatever format the user
|
||||
supplies. */
|
||||
vvp_vector4_t put_val (width);
|
||||
|
||||
switch (val->format) {
|
||||
|
||||
case vpiVectorVal:
|
||||
for (unsigned widx = 0; widx < width; widx += 32) {
|
||||
p_vpi_vecval cur = val->value.vector + (widx/32);
|
||||
for (unsigned idx = widx
|
||||
; idx < width && idx < widx+32
|
||||
; idx += 1) {
|
||||
int aval = (cur->aval >> (idx%32)) & 1;
|
||||
int bval = (cur->bval >> (idx%32)) & 1;
|
||||
unsigned char val = (bval<<1) | (aval^bval);
|
||||
memory_set(rfp->mem->mem, bidx+idx, val);
|
||||
for (unsigned idx = 0 ; idx < width ; idx += 1) {
|
||||
p_vpi_vecval cur = val->value.vector + (idx/32);
|
||||
int aval = (cur->aval >> (idx%32)) & 1;
|
||||
int bval = (cur->bval >> (idx%32)) & 1;
|
||||
|
||||
/* Check this bit value conversion. This is
|
||||
specifically defined by the IEEE1364 standard. */
|
||||
vvp_bit4_t bit;
|
||||
if (bval) {
|
||||
bit = aval? BIT4_Z : BIT4_X;
|
||||
} else {
|
||||
bit = aval? BIT4_1 : BIT4_0;
|
||||
}
|
||||
put_val.set_bit(idx, bit);
|
||||
}
|
||||
break;
|
||||
|
||||
#if 0
|
||||
case vpiIntVal:
|
||||
for (unsigned widx = 0; widx < width; widx += 32) {
|
||||
int cur = val->value.integer;
|
||||
|
|
@ -273,7 +281,8 @@ static vpiHandle memory_word_put(vpiHandle ref, p_vpi_value val)
|
|||
}
|
||||
}
|
||||
break;
|
||||
|
||||
#endif
|
||||
#if 0
|
||||
/* If the caller tries to set a HexStrVal, convert it to
|
||||
bits and write the bits into the word. */
|
||||
case vpiHexStrVal: {
|
||||
|
|
@ -290,7 +299,8 @@ static vpiHandle memory_word_put(vpiHandle ref, p_vpi_value val)
|
|||
delete[]bits;
|
||||
break;
|
||||
}
|
||||
|
||||
#endif
|
||||
#if 0
|
||||
case vpiDecStrVal: {
|
||||
unsigned char*bits = new unsigned char[width];
|
||||
vpip_dec_str_to_bits(bits, width, val->value.str, false);
|
||||
|
|
@ -302,7 +312,8 @@ static vpiHandle memory_word_put(vpiHandle ref, p_vpi_value val)
|
|||
delete[]bits;
|
||||
break;
|
||||
}
|
||||
|
||||
#endif
|
||||
#if 0
|
||||
case vpiOctStrVal: {
|
||||
unsigned char*bits = new unsigned char[(width+3) / 4];
|
||||
vpip_oct_str_to_bits(bits, width, val->value.str, false);
|
||||
|
|
@ -317,7 +328,8 @@ static vpiHandle memory_word_put(vpiHandle ref, p_vpi_value val)
|
|||
delete[]bits;
|
||||
break;
|
||||
}
|
||||
|
||||
#endif
|
||||
#if 0
|
||||
case vpiBinStrVal: {
|
||||
unsigned char*bits = new unsigned char[(width+3) / 4];
|
||||
vpip_bin_str_to_bits(bits, width, val->value.str, false);
|
||||
|
|
@ -332,11 +344,12 @@ static vpiHandle memory_word_put(vpiHandle ref, p_vpi_value val)
|
|||
delete[]bits;
|
||||
break;
|
||||
}
|
||||
|
||||
#endif
|
||||
default:
|
||||
assert(0);
|
||||
}
|
||||
|
||||
memory_set_word(rfp->mem->mem, word_addr, put_val);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
@ -347,7 +360,7 @@ static char* memory_word_get_str(int code, vpiHandle ref)
|
|||
struct __vpiMemoryWord*rfp = (struct __vpiMemoryWord*)ref;
|
||||
|
||||
char *bn = strdup(vpi_get_str(vpiFullName, &rfp->mem->scope->base));
|
||||
char *nm = memory_name(rfp->mem->mem);
|
||||
const char *nm = rfp->mem->name;
|
||||
|
||||
char *rbuf = need_result_buf(strlen(bn) + strlen(nm) + 10 + 4, RBUF_STR);
|
||||
|
||||
|
|
@ -374,9 +387,10 @@ static void memory_word_get_value(vpiHandle ref, s_vpi_value*vp)
|
|||
struct __vpiMemoryWord*rfp = (struct __vpiMemoryWord*)ref;
|
||||
assert(rfp->base.vpi_type->type_code==vpiMemoryWord);
|
||||
|
||||
unsigned width = memory_data_width(rfp->mem->mem);
|
||||
unsigned word_offset = memory_root(rfp->mem->mem);
|
||||
unsigned bidx = (rfp->index.value - word_offset) * ((width+3)&~3);
|
||||
unsigned width = memory_word_width(rfp->mem->mem);
|
||||
unsigned word_address = rfp->index.value;
|
||||
|
||||
vvp_vector4_t word_val = memory_get_word(rfp->mem->mem, word_address);
|
||||
|
||||
char *rbuf = 0;
|
||||
|
||||
|
|
@ -385,16 +399,16 @@ static void memory_word_get_value(vpiHandle ref, s_vpi_value*vp)
|
|||
assert("format not implemented");
|
||||
|
||||
case vpiBinStrVal:
|
||||
rbuf = need_result_buf(width+1, RBUF_VAL);
|
||||
for (unsigned idx = 0 ; idx < width ; idx += 1) {
|
||||
unsigned bit = memory_get(rfp->mem->mem, bidx+idx);
|
||||
|
||||
rbuf = need_result_buf(width+1, RBUF_VAL);
|
||||
for (unsigned idx = 0 ; idx < width ; idx += 1) {
|
||||
vvp_bit4_t bit = word_val.value(idx);
|
||||
rbuf[width-idx-1] = "01xz"[bit];
|
||||
}
|
||||
rbuf[width] = 0;
|
||||
vp->value.str = rbuf;
|
||||
break;
|
||||
|
||||
}
|
||||
rbuf[width] = 0;
|
||||
vp->value.str = rbuf;
|
||||
break;
|
||||
#if 0
|
||||
/* XXXX Needs to be converted. */
|
||||
case vpiOctStrVal: {
|
||||
unsigned hwid = (width+2) / 3;
|
||||
unsigned char*bits = new unsigned char[width];
|
||||
|
|
@ -416,7 +430,9 @@ static void memory_word_get_value(vpiHandle ref, s_vpi_value*vp)
|
|||
vp->value.str = rbuf;
|
||||
break;
|
||||
}
|
||||
|
||||
#endif
|
||||
#if 0
|
||||
/* XXXX Needs to be converted. */
|
||||
case vpiHexStrVal: {
|
||||
unsigned hval, hwid;
|
||||
hwid = (width + 3) / 4;
|
||||
|
|
@ -455,7 +471,8 @@ static void memory_word_get_value(vpiHandle ref, s_vpi_value*vp)
|
|||
vp->value.str = rbuf;
|
||||
break;
|
||||
}
|
||||
|
||||
#endif
|
||||
#if 0
|
||||
case vpiDecStrVal: {
|
||||
unsigned char*bits = new unsigned char[width];
|
||||
|
||||
|
|
@ -469,7 +486,8 @@ static void memory_word_get_value(vpiHandle ref, s_vpi_value*vp)
|
|||
vp->value.str = rbuf;
|
||||
break;
|
||||
}
|
||||
|
||||
#endif
|
||||
#if 0
|
||||
case vpiIntVal:
|
||||
assert(width <= 8 * sizeof vp->value.integer);
|
||||
|
||||
|
|
@ -485,7 +503,8 @@ static void memory_word_get_value(vpiHandle ref, s_vpi_value*vp)
|
|||
vp->value.integer |= bit << idx;
|
||||
}
|
||||
break;
|
||||
|
||||
#endif
|
||||
#if 0
|
||||
case vpiVectorVal: {
|
||||
unsigned hwid = (width - 1)/32 + 1;
|
||||
|
||||
|
|
@ -519,7 +538,8 @@ static void memory_word_get_value(vpiHandle ref, s_vpi_value*vp)
|
|||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -550,8 +570,7 @@ static void memory_make_word_handles(struct __vpiMemory*rfp)
|
|||
if (rfp->words != 0)
|
||||
return;
|
||||
|
||||
unsigned word_count = memory_size(rfp->mem);
|
||||
unsigned word_offset = memory_root(rfp->mem);
|
||||
unsigned word_count = memory_word_count(rfp->mem);
|
||||
|
||||
rfp->words = (struct __vpiMemoryWord*)
|
||||
calloc(word_count, sizeof (struct __vpiMemoryWord));
|
||||
|
|
@ -560,11 +579,11 @@ static void memory_make_word_handles(struct __vpiMemory*rfp)
|
|||
struct __vpiMemoryWord*cur = rfp->words + idx;
|
||||
cur->base.vpi_type = &vpip_memory_word_rt;
|
||||
cur->mem = rfp;
|
||||
vpip_make_dec_const(&cur->index, idx + word_offset);
|
||||
vpip_make_dec_const(&cur->index, idx);
|
||||
}
|
||||
}
|
||||
|
||||
vpiHandle vpip_make_memory(vvp_memory_t mem)
|
||||
vpiHandle vpip_make_memory(vvp_memory_t mem, const char*name)
|
||||
{
|
||||
struct __vpiMemory*obj = (struct __vpiMemory*)
|
||||
malloc(sizeof(struct __vpiMemory));
|
||||
|
|
@ -573,8 +592,10 @@ vpiHandle vpip_make_memory(vvp_memory_t mem)
|
|||
obj->base.vpi_type = &vpip_memory_rt;
|
||||
obj->scope = vpip_peek_current_scope();
|
||||
obj->mem = mem;
|
||||
vpip_make_dec_const(&obj->left_range, memory_left_range(mem));
|
||||
vpip_make_dec_const(&obj->right_range, memory_right_range(mem));
|
||||
obj->name = vpip_name_string(name);
|
||||
|
||||
vpip_make_dec_const(&obj->left_range, memory_left_range(mem, 0));
|
||||
vpip_make_dec_const(&obj->right_range, memory_right_range(mem, 0));
|
||||
vpip_make_dec_const(&obj->word_left_range, memory_word_left_range(mem));
|
||||
vpip_make_dec_const(&obj->word_right_range,memory_word_right_range(mem));
|
||||
|
||||
|
|
@ -585,6 +606,9 @@ vpiHandle vpip_make_memory(vvp_memory_t mem)
|
|||
|
||||
/*
|
||||
* $Log: vpi_memory.cc,v $
|
||||
* Revision 1.24 2005/03/03 04:33:10 steve
|
||||
* Rearrange how memories are supported as vvp_vector4 arrays.
|
||||
*
|
||||
* Revision 1.23 2004/05/19 03:30:46 steve
|
||||
* Support delayed/non-blocking assignment to reals and others.
|
||||
*
|
||||
|
|
@ -625,41 +649,4 @@ vpiHandle vpip_make_memory(vvp_memory_t mem)
|
|||
* temporary buffers for *_get_str() data,
|
||||
* dynamic storage for vpi_get_data() in memory types
|
||||
* shared with signal white space
|
||||
*
|
||||
* Revision 1.11 2002/07/01 15:36:12 steve
|
||||
* Limit word writing to vector limits.
|
||||
*
|
||||
* Revision 1.10 2002/06/30 04:35:47 steve
|
||||
* Get vpiVectorVal for memories.
|
||||
*
|
||||
* Revision 1.9 2002/05/17 04:12:19 steve
|
||||
* Rewire vpiMemory and vpiMemoryWord handles to
|
||||
* support proper iteration of words, and the
|
||||
* vpiIndex value.
|
||||
*
|
||||
* Revision 1.8 2002/05/11 04:39:35 steve
|
||||
* Set and get memory words by string value.
|
||||
*
|
||||
* Revision 1.7 2002/05/10 16:00:57 steve
|
||||
* Support scope iterate over vpiNet,vpiReg/vpiMemory.
|
||||
*
|
||||
* Revision 1.6 2002/05/03 15:44:11 steve
|
||||
* Add vpiModule iterator to vpiScope objects.
|
||||
*
|
||||
* Revision 1.5 2002/02/06 04:48:34 steve
|
||||
* get bin or hex string values of memory words.
|
||||
*
|
||||
* Revision 1.4 2002/01/31 04:28:17 steve
|
||||
* Full support for $readmem ranges (Tom Verbeure)
|
||||
*
|
||||
* Revision 1.3 2001/12/07 23:23:05 steve
|
||||
* vpi_put_value of vpiIntVal for memory words.
|
||||
*
|
||||
* Revision 1.2 2001/11/09 03:39:07 steve
|
||||
* Support vpiIntVal from memory.
|
||||
*
|
||||
* Revision 1.1 2001/05/08 23:59:33 steve
|
||||
* Add ivl and vvp.tgt support for memories in
|
||||
* expressions and l-values. (Stephan Boettcher)
|
||||
*
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@
|
|||
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
|
||||
*/
|
||||
#ifdef HAVE_CVS_IDENT
|
||||
#ident "$Id: vpi_priv.h,v 1.60 2004/12/11 02:31:30 steve Exp $"
|
||||
#ident "$Id: vpi_priv.h,v 1.61 2005/03/03 04:33:10 steve Exp $"
|
||||
#endif
|
||||
|
||||
# include "vpi_user.h"
|
||||
|
|
@ -216,7 +216,7 @@ extern void vpip_real_value_change(struct __vpiCallback*cbh,
|
|||
* the memory.
|
||||
*/
|
||||
|
||||
extern vpiHandle vpip_make_memory(vvp_memory_t mem);
|
||||
extern vpiHandle vpip_make_memory(vvp_memory_t mem, const char*name);
|
||||
|
||||
/*
|
||||
* These are the various variable types.
|
||||
|
|
@ -417,6 +417,9 @@ extern char *need_result_buf(unsigned cnt, vpi_rbuf_t type);
|
|||
|
||||
/*
|
||||
* $Log: vpi_priv.h,v $
|
||||
* Revision 1.61 2005/03/03 04:33:10 steve
|
||||
* Rearrange how memories are supported as vvp_vector4 arrays.
|
||||
*
|
||||
* Revision 1.60 2004/12/11 02:31:30 steve
|
||||
* Rework of internals to carry vectors through nexus instead
|
||||
* of single bits. Make the ivl, tgt-vvp and vvp initial changes
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@
|
|||
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
|
||||
*/
|
||||
#ifdef HAVE_CVS_IDENT
|
||||
#ident "$Id: vpi_signal.cc,v 1.63 2004/12/11 02:31:30 steve Exp $"
|
||||
#ident "$Id: vpi_signal.cc,v 1.64 2005/03/03 04:33:10 steve Exp $"
|
||||
#endif
|
||||
|
||||
/*
|
||||
|
|
@ -491,27 +491,26 @@ static vpiHandle signal_put_value(vpiHandle ref, s_vpi_value*vp)
|
|||
|
||||
rfp = (struct __vpiSignal*)ref;
|
||||
|
||||
vvp_net_ptr_t destination (rfp->node, 0);
|
||||
wid = (rfp->msb >= rfp->lsb)
|
||||
? (rfp->msb - rfp->lsb + 1)
|
||||
: (rfp->lsb - rfp->msb + 1);
|
||||
|
||||
vvp_vector4_t val (wid);
|
||||
|
||||
switch (vp->format) {
|
||||
|
||||
case vpiIntVal: {
|
||||
if (wid > 8*sizeof(long)) {
|
||||
fprintf(stderr, "internal error: wid(%u) "
|
||||
"too large.\n", wid);
|
||||
assert(0);
|
||||
}
|
||||
|
||||
long val = vp->value.integer;
|
||||
long vpi_val = vp->value.integer;
|
||||
for (unsigned idx = 0 ; idx < wid ; idx += 1) {
|
||||
functor_poke(rfp, idx, val&1, (val&1)? St1 : St0, 0);
|
||||
val >>= 1;
|
||||
vvp_bit4_t bit = vpi_val&1 ? BIT4_1 : BIT4_0;
|
||||
val.set_bit(idx, bit);
|
||||
vpi_val >>= 1;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
#if 0
|
||||
case vpiScalarVal:
|
||||
switch (vp->value.scalar) {
|
||||
case vpi0:
|
||||
|
|
@ -530,7 +529,8 @@ static vpiHandle signal_put_value(vpiHandle ref, s_vpi_value*vp)
|
|||
assert(0);
|
||||
}
|
||||
break;
|
||||
|
||||
#endif
|
||||
#if 0
|
||||
case vpiVectorVal:
|
||||
for (unsigned idx = 0 ; idx < wid ; idx += 1) {
|
||||
unsigned long aval = vp->value.vector[idx/32].aval;
|
||||
|
|
@ -554,7 +554,8 @@ static vpiHandle signal_put_value(vpiHandle ref, s_vpi_value*vp)
|
|||
}
|
||||
}
|
||||
break;
|
||||
|
||||
#endif
|
||||
#if 0
|
||||
case vpiBinStrVal: {
|
||||
unsigned char*bits = new unsigned char[(wid+3) / 4];
|
||||
vpip_bin_str_to_bits(bits, wid, vp->value.str, false);
|
||||
|
|
@ -583,7 +584,8 @@ static vpiHandle signal_put_value(vpiHandle ref, s_vpi_value*vp)
|
|||
delete[]bits;
|
||||
break;
|
||||
}
|
||||
|
||||
#endif
|
||||
#if 0
|
||||
case vpiOctStrVal: {
|
||||
unsigned char*bits = new unsigned char[(wid+3) / 4];
|
||||
vpip_oct_str_to_bits(bits, wid, vp->value.str, false);
|
||||
|
|
@ -612,7 +614,8 @@ static vpiHandle signal_put_value(vpiHandle ref, s_vpi_value*vp)
|
|||
delete[]bits;
|
||||
break;
|
||||
}
|
||||
|
||||
#endif
|
||||
#if 0
|
||||
case vpiHexStrVal: {
|
||||
unsigned char*bits = new unsigned char[(wid+3) / 4];
|
||||
vpip_hex_str_to_bits(bits, wid, vp->value.str, false);
|
||||
|
|
@ -641,7 +644,8 @@ static vpiHandle signal_put_value(vpiHandle ref, s_vpi_value*vp)
|
|||
delete[]bits;
|
||||
break;
|
||||
}
|
||||
|
||||
#endif
|
||||
#if 0
|
||||
case vpiDecStrVal: {
|
||||
unsigned char*bits = new unsigned char[wid];
|
||||
vpip_dec_str_to_bits(bits, wid, vp->value.str, false);
|
||||
|
|
@ -667,7 +671,7 @@ static vpiHandle signal_put_value(vpiHandle ref, s_vpi_value*vp)
|
|||
delete[]bits;
|
||||
break;
|
||||
}
|
||||
|
||||
#endif
|
||||
case vpiStringVal:
|
||||
signal_put_stringval(rfp, wid, vp->value.str);
|
||||
break;
|
||||
|
|
@ -681,6 +685,8 @@ static vpiHandle signal_put_value(vpiHandle ref, s_vpi_value*vp)
|
|||
|
||||
}
|
||||
|
||||
vvp_send_vec4(destination, val);
|
||||
|
||||
return ref;
|
||||
}
|
||||
|
||||
|
|
@ -772,6 +778,9 @@ vpiHandle vpip_make_net(const char*name, int msb, int lsb,
|
|||
|
||||
/*
|
||||
* $Log: vpi_signal.cc,v $
|
||||
* Revision 1.64 2005/03/03 04:33:10 steve
|
||||
* Rearrange how memories are supported as vvp_vector4 arrays.
|
||||
*
|
||||
* Revision 1.63 2004/12/11 02:31:30 steve
|
||||
* Rework of internals to carry vectors through nexus instead
|
||||
* of single bits. Make the ivl, tgt-vvp and vvp initial changes
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@
|
|||
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
|
||||
*/
|
||||
#ifdef HAVE_CVS_IDENT
|
||||
#ident "$Id: vthread.cc,v 1.129 2005/02/14 01:50:23 steve Exp $"
|
||||
#ident "$Id: vthread.cc,v 1.130 2005/03/03 04:33:10 steve Exp $"
|
||||
#endif
|
||||
|
||||
# include "config.h"
|
||||
|
|
@ -613,8 +613,18 @@ bool of_ASSIGN_X0(vthread_t thr, vvp_code_t cp)
|
|||
|
||||
bool of_ASSIGN_MEM(vthread_t thr, vvp_code_t cp)
|
||||
{
|
||||
#if 0
|
||||
unsigned char bit_val = thr_get_bit(thr, cp->bit_idx[1]);
|
||||
schedule_memory(cp->mem, thr->words[3].w_int, bit_val, cp->bit_idx[0]);
|
||||
#else
|
||||
fprintf(stderr, "XXXX %%assign/m is obsolete.\n");
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
|
||||
bool of_ASSIGN_MV(vthread_t thr, vvp_code_t cp)
|
||||
{
|
||||
fprintf(stderr, "XXXX %%assign/mv not implemented yet\n");
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
@ -1670,12 +1680,44 @@ bool of_JOIN(vthread_t thr, vvp_code_t cp)
|
|||
|
||||
bool of_LOAD_MEM(vthread_t thr, vvp_code_t cp)
|
||||
{
|
||||
#if 0
|
||||
assert(cp->bit_idx[0] >= 4);
|
||||
unsigned char val = memory_get(cp->mem, thr->words[3].w_int);
|
||||
thr_put_bit(thr, cp->bit_idx[0], val);
|
||||
#else
|
||||
fprintf(stderr, "XXXX %%load/m is obsolete\n");
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
* %load/mv <bit>, <mem-label>, <wid> ;
|
||||
*
|
||||
* <bit> is the thread bit address for the result
|
||||
* <mem-label> is the memory device to access, and
|
||||
* <wid> is the width of the word to read.
|
||||
*
|
||||
* The address of the word in the memory is in index register 3.
|
||||
*/
|
||||
bool of_LOAD_MV(vthread_t thr, vvp_code_t cp)
|
||||
{
|
||||
unsigned bit = cp->bit_idx[0];
|
||||
unsigned wid = cp->bit_idx[1];
|
||||
unsigned adr = thr->words[3].w_int;
|
||||
|
||||
vvp_vector4_t word = memory_get_word(cp->mem, adr);
|
||||
|
||||
assert(word.size() == wid);
|
||||
|
||||
for (unsigned idx = 0 ; idx < wid ; idx += 1, bit += 1) {
|
||||
vvp_bit4_t val = word.value(idx);
|
||||
thr_put_bit(thr, bit, val);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* %load/nx <bit>, <vpi-label>, <idx> ; Load net/indexed.
|
||||
*
|
||||
|
|
@ -2607,14 +2649,25 @@ bool of_RELEASE_REG(vthread_t thr, vvp_code_t cp)
|
|||
|
||||
static const unsigned char strong_values[4] = {St0, St1, StX, HiZ};
|
||||
|
||||
bool of_SET_MEM(vthread_t thr, vvp_code_t cp)
|
||||
/*
|
||||
* This implements the "%set/mv <label>, <bit>, <wid>" instruction. In
|
||||
* this case, the <label> is a memory label, and the <bit> and <wid>
|
||||
* are the thread vector of a value to be written in.
|
||||
*/
|
||||
bool of_SET_MV(vthread_t thr, vvp_code_t cp)
|
||||
{
|
||||
unsigned char val = thr_get_bit(thr, cp->bit_idx[0]);
|
||||
memory_set(cp->mem, thr->words[3].w_int, val);
|
||||
unsigned bit = cp->bit_idx[0];
|
||||
unsigned wid = cp->bit_idx[1];
|
||||
unsigned adr = thr->words[3].w_int;
|
||||
|
||||
/* Make a vector of the desired width. */
|
||||
vvp_vector4_t value = vthread_bits_to_vector(thr, bit, wid);
|
||||
|
||||
memory_set_word(cp->mem, adr, value);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* This implements the "%set/v <label>, <bit>, <wid>" instruction.
|
||||
*
|
||||
|
|
@ -3042,6 +3095,9 @@ bool of_JOIN_UFUNC(vthread_t thr, vvp_code_t cp)
|
|||
|
||||
/*
|
||||
* $Log: vthread.cc,v $
|
||||
* Revision 1.130 2005/03/03 04:33:10 steve
|
||||
* Rearrange how memories are supported as vvp_vector4 arrays.
|
||||
*
|
||||
* Revision 1.129 2005/02/14 01:50:23 steve
|
||||
* Signals may receive part vectors from %set/x0
|
||||
* instructions. Re-implement the %set/x0 to do
|
||||
|
|
|
|||
Loading…
Reference in New Issue