2549 lines
66 KiB
C
2549 lines
66 KiB
C
/*
|
|
* Copyright (c) 2001-2008 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
|
|
*/
|
|
|
|
# include "vvp_priv.h"
|
|
#ifdef HAVE_MALLOC_H
|
|
# include <malloc.h>
|
|
#endif
|
|
# include <stdlib.h>
|
|
# include <math.h>
|
|
# include <string.h>
|
|
# include <inttypes.h>
|
|
# include <assert.h>
|
|
|
|
struct vvp_nexus_data {
|
|
/* draw_net_input uses this */
|
|
const char*net_input;
|
|
unsigned drivers_count;
|
|
int flags;
|
|
/* draw_net_in_scope uses these to identify the controlling word. */
|
|
ivl_signal_t net;
|
|
unsigned net_word;
|
|
};
|
|
#define VVP_NEXUS_DATA_STR 0x0001
|
|
|
|
|
|
static struct vvp_nexus_data*new_nexus_data()
|
|
{
|
|
struct vvp_nexus_data*data = calloc(1, sizeof(struct vvp_nexus_data));
|
|
return data;
|
|
}
|
|
|
|
/*
|
|
* Escape non-symbol characters in ids, and quotes in strings.
|
|
*/
|
|
|
|
inline static char hex_digit(unsigned i)
|
|
{
|
|
i &= 0xf;
|
|
return i>=10 ? i-10+'A' : i+'0';
|
|
}
|
|
|
|
const char *vvp_mangle_id(const char *id)
|
|
{
|
|
static char *out = 0x0;
|
|
static size_t out_len;
|
|
|
|
int nesc = 0;
|
|
int iout = 0;
|
|
const char *inp = id;
|
|
|
|
const char nosym[] = "!\"#%&'()*+,-/:;<=>?@[\\]^`{|}~";
|
|
|
|
char *se = strpbrk(inp, nosym);
|
|
if (!se)
|
|
return id;
|
|
|
|
do {
|
|
int n = se - inp;
|
|
unsigned int nlen = strlen(id) + 4*(++nesc) + 1;
|
|
if (out_len < nlen) {
|
|
out = realloc(out, nlen);
|
|
assert(out);
|
|
out_len = nlen;
|
|
}
|
|
if (n) {
|
|
strncpy(out+iout, inp, n);
|
|
iout += n;
|
|
}
|
|
inp += n+1;
|
|
out[iout++] = '\\';
|
|
switch (*se) {
|
|
case '\\':
|
|
case '/':
|
|
case '<':
|
|
case '>':
|
|
out[iout++] = *se;
|
|
break;
|
|
default:
|
|
out[iout++] = 'x';
|
|
out[iout++] = hex_digit(*se >> 4);
|
|
out[iout++] = hex_digit(*se);
|
|
break;
|
|
}
|
|
|
|
se = strpbrk(inp, nosym);
|
|
} while (se);
|
|
|
|
strcpy(out+iout, inp);
|
|
return out;
|
|
}
|
|
|
|
const char *vvp_mangle_name(const char *id)
|
|
{
|
|
static char *out = 0x0;
|
|
static size_t out_len;
|
|
|
|
int nesc = 0;
|
|
int iout = 0;
|
|
const char *inp = id;
|
|
|
|
const char nosym[] = "\"\\";
|
|
|
|
char *se = strpbrk(inp, nosym);
|
|
if (!se)
|
|
return id;
|
|
|
|
do {
|
|
int n = se - inp;
|
|
unsigned int nlen = strlen(id) + 2*(++nesc) + 1;
|
|
if (out_len < nlen) {
|
|
out = realloc(out, nlen);
|
|
assert(out);
|
|
out_len = nlen;
|
|
}
|
|
if (n) {
|
|
strncpy(out+iout, inp, n);
|
|
iout += n;
|
|
}
|
|
inp += n+1;
|
|
out[iout++] = '\\';
|
|
out[iout++] = *se;
|
|
|
|
se = strpbrk(inp, nosym);
|
|
} while (se);
|
|
|
|
strcpy(out+iout, inp);
|
|
return out;
|
|
}
|
|
|
|
static void draw_C4_repeated_constant(char bit_char, unsigned width)
|
|
{
|
|
unsigned idx;
|
|
|
|
fprintf(vvp_out, "C4<");
|
|
for (idx = 0 ; idx < width ; idx += 1)
|
|
fprintf(vvp_out, "%c", bit_char);
|
|
|
|
fprintf(vvp_out, ">");
|
|
}
|
|
|
|
static void str_repeat(char*buf, const char*str, unsigned rpt)
|
|
{
|
|
unsigned idx;
|
|
size_t len = strlen(str);
|
|
for (idx = 0 ; idx < rpt ; idx += 1) {
|
|
strcpy(buf, str);
|
|
buf += len;
|
|
}
|
|
}
|
|
|
|
/* REMOVE ME: vvp_signal_label should not be used. DEAD CODE
|
|
* Given a signal, generate a string name that is suitable for use as
|
|
* a label. The only rule is that the same signal will always have the
|
|
* same label. The result is stored in static memory, so remember to
|
|
* copy it out.
|
|
*/
|
|
const char* vvp_signal_label(ivl_signal_t sig)
|
|
{
|
|
static char buf[32];
|
|
sprintf(buf, "%p", sig);
|
|
return buf;
|
|
}
|
|
|
|
ivl_signal_t signal_of_nexus(ivl_nexus_t nex, unsigned*word)
|
|
{
|
|
unsigned idx;
|
|
for (idx = 0 ; idx < ivl_nexus_ptrs(nex) ; idx += 1) {
|
|
ivl_nexus_ptr_t ptr = ivl_nexus_ptr(nex, idx);
|
|
ivl_signal_t sig = ivl_nexus_ptr_sig(ptr);
|
|
if (sig == 0)
|
|
continue;
|
|
if (ivl_signal_local(sig))
|
|
continue;
|
|
*word = ivl_nexus_ptr_pin(ptr);
|
|
return sig;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
ivl_signal_type_t signal_type_of_nexus(ivl_nexus_t nex)
|
|
{
|
|
unsigned idx;
|
|
ivl_signal_type_t out = IVL_SIT_TRI;
|
|
|
|
for (idx = 0 ; idx < ivl_nexus_ptrs(nex) ; idx += 1) {
|
|
ivl_signal_type_t stype;
|
|
ivl_nexus_ptr_t ptr = ivl_nexus_ptr(nex, idx);
|
|
ivl_signal_t sig = ivl_nexus_ptr_sig(ptr);
|
|
if (sig == 0)
|
|
continue;
|
|
|
|
stype = ivl_signal_type(sig);
|
|
if (stype == IVL_SIT_REG)
|
|
continue;
|
|
if (stype == IVL_SIT_TRI)
|
|
continue;
|
|
if (stype == IVL_SIT_NONE)
|
|
continue;
|
|
out = stype;
|
|
}
|
|
|
|
return out;
|
|
}
|
|
|
|
unsigned width_of_nexus(ivl_nexus_t nex)
|
|
{
|
|
unsigned idx;
|
|
|
|
for (idx = 0 ; idx < ivl_nexus_ptrs(nex) ; idx += 1) {
|
|
ivl_nexus_ptr_t ptr = ivl_nexus_ptr(nex, idx);
|
|
ivl_signal_t sig = ivl_nexus_ptr_sig(ptr);
|
|
if (sig != 0)
|
|
return ivl_signal_width(sig);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
ivl_variable_type_t data_type_of_nexus(ivl_nexus_t nex)
|
|
{
|
|
unsigned idx;
|
|
for (idx = 0 ; idx < ivl_nexus_ptrs(nex) ; idx += 1) {
|
|
ivl_nexus_ptr_t ptr = ivl_nexus_ptr(nex, idx);
|
|
ivl_signal_t sig = ivl_nexus_ptr_sig(ptr);
|
|
if (sig != 0)
|
|
return ivl_signal_data_type(sig);
|
|
}
|
|
|
|
/* shouldn't happen! */
|
|
return IVL_VT_NO_TYPE;
|
|
}
|
|
|
|
|
|
ivl_nexus_ptr_t ivl_logic_pin_ptr(ivl_net_logic_t net, unsigned pin)
|
|
{
|
|
ivl_nexus_t nex = ivl_logic_pin(net, pin);
|
|
unsigned idx;
|
|
|
|
for (idx = 0 ; idx < ivl_nexus_ptrs(nex) ; idx += 1) {
|
|
ivl_nexus_ptr_t ptr = ivl_nexus_ptr(nex, idx);
|
|
ivl_net_logic_t tmp = ivl_nexus_ptr_log(ptr);
|
|
if (tmp == 0)
|
|
continue;
|
|
if (tmp != net)
|
|
continue;
|
|
if (ivl_nexus_ptr_pin(ptr) != pin)
|
|
continue;
|
|
|
|
return ptr;
|
|
}
|
|
assert(0);
|
|
return 0;
|
|
}
|
|
|
|
const char*drive_string(ivl_drive_t drive)
|
|
{
|
|
switch (drive) {
|
|
case IVL_DR_HiZ:
|
|
return "";
|
|
case IVL_DR_SMALL:
|
|
return "sm";
|
|
case IVL_DR_MEDIUM:
|
|
return "me";
|
|
case IVL_DR_WEAK:
|
|
return "we";
|
|
case IVL_DR_LARGE:
|
|
return "la";
|
|
case IVL_DR_PULL:
|
|
return "pu";
|
|
case IVL_DR_STRONG:
|
|
return "";
|
|
case IVL_DR_SUPPLY:
|
|
return "su";
|
|
}
|
|
|
|
return "";
|
|
}
|
|
|
|
|
|
/*
|
|
* The draw_scope function draws the major functional items within a
|
|
* scope. This includes the scopes themselves, of course. All the
|
|
* other functions in this file are in support of that task.
|
|
*/
|
|
|
|
|
|
/*
|
|
* NEXUS
|
|
* ivl builds up the netlist into objects connected together by
|
|
* ivl_nexus_t objects. The nexus receives all the drivers of the
|
|
* point in the net and resolves the value. The result is then sent to
|
|
* all the nets that are connected to the nexus. The nets, then, are
|
|
* read to get the value of the nexus.
|
|
*
|
|
* NETS
|
|
* Nets are interesting and special, because a nexus may be connected
|
|
* to several of them at once. This can happen, for example, as an
|
|
* artifact of module port connects, where the inside and the outside
|
|
* of the module are connected through an in-out port. (In fact, ivl
|
|
* will simply connect signals that are bound through a port, because
|
|
* the input/output/inout properties are enforced as compile time.)
|
|
*
|
|
* This case is handled by choosing one to receive the value of the
|
|
* nexus. This one then feeds to another net at the nexus, and so
|
|
* on. The last net is selected as the output of the nexus.
|
|
*/
|
|
|
|
/*
|
|
* This tests a bufz device against an output receiver, and determines
|
|
* if the device can be skipped. If this function returns true, then a
|
|
* gate will be generated for this node. Otherwise, the code generator
|
|
* will connect its input to its output and skip the gate.
|
|
*/
|
|
static int can_elide_bufz(ivl_net_logic_t net, ivl_nexus_ptr_t nptr)
|
|
{
|
|
ivl_nexus_t in_n;
|
|
unsigned idx;
|
|
|
|
/* These are the drives we expect. */
|
|
ivl_drive_t dr0 = ivl_nexus_ptr_drive0(nptr);
|
|
ivl_drive_t dr1 = ivl_nexus_ptr_drive1(nptr);
|
|
int drive_count = 0;
|
|
|
|
/* If the gate carries a delay, it must remain. */
|
|
if (ivl_logic_delay(net, 0) != 0)
|
|
return 0;
|
|
|
|
/* If the input is connected to the output, then do not elide
|
|
the gate. This is some sort of cycle. */
|
|
if (ivl_logic_pin(net, 0) == ivl_logic_pin(net, 1))
|
|
return 0;
|
|
|
|
in_n = ivl_logic_pin(net, 1);
|
|
for (idx = 0 ; idx < ivl_nexus_ptrs(in_n) ; idx += 1) {
|
|
ivl_nexus_ptr_t in_np = ivl_nexus_ptr(in_n, idx);
|
|
if (ivl_nexus_ptr_log(in_np) == net)
|
|
continue;
|
|
|
|
/* If the driver for the source does not match the
|
|
expected drive, then we need to keep the bufz. This
|
|
test also catches the case that the input device is
|
|
really also an input, as that device will have a
|
|
drive of HiZ. We need to keep BUFZ devices in that
|
|
case in order to prevent back-flow of data. */
|
|
if (ivl_nexus_ptr_drive0(in_np) != dr0)
|
|
return 0;
|
|
if (ivl_nexus_ptr_drive1(in_np) != dr1)
|
|
return 0;
|
|
|
|
drive_count += 1;
|
|
}
|
|
|
|
/* If the BUFZ input has multiple drivers on its input, then
|
|
we need to keep this device in order to hide the
|
|
resolution. */
|
|
if (drive_count != 1)
|
|
return 0;
|
|
|
|
return 1;
|
|
}
|
|
|
|
/*
|
|
* Given a nexus, look for a signal that has module delay
|
|
* paths. Return that signal. (There should be no more than 1.) If we
|
|
* don't find any, then return nil.
|
|
*/
|
|
static ivl_signal_t find_modpath(ivl_nexus_t nex)
|
|
{
|
|
unsigned idx;
|
|
for (idx = 0 ; idx < ivl_nexus_ptrs(nex) ; idx += 1) {
|
|
ivl_nexus_ptr_t ptr = ivl_nexus_ptr(nex,idx);
|
|
ivl_signal_t sig = ivl_nexus_ptr_sig(ptr);
|
|
if (sig == 0)
|
|
continue;
|
|
if (ivl_signal_npath(sig) == 0)
|
|
continue;
|
|
|
|
return sig;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static char* draw_C4_to_string(ivl_net_const_t cptr)
|
|
{
|
|
const char*bits = ivl_const_bits(cptr);
|
|
unsigned idx;
|
|
|
|
size_t result_len = 5 + ivl_const_width(cptr);
|
|
char*result = malloc(result_len);
|
|
char*dp = result;
|
|
strcpy(dp, "C4<");
|
|
dp += strlen(dp);
|
|
|
|
for (idx = 0 ; idx < ivl_const_width(cptr) ; idx += 1) {
|
|
char bitchar = bits[ivl_const_width(cptr)-idx-1];
|
|
*dp++ = bitchar;
|
|
assert((dp - result) < result_len);
|
|
}
|
|
|
|
strcpy(dp, ">");
|
|
return result;
|
|
}
|
|
|
|
static char* draw_C8_to_string(ivl_net_const_t cptr,
|
|
ivl_drive_t dr0, ivl_drive_t dr1)
|
|
{
|
|
size_t nresult = 5 + 3*ivl_const_width(cptr);
|
|
char*result = malloc(nresult);
|
|
const char*bits = ivl_const_bits(cptr);
|
|
unsigned idx;
|
|
|
|
char dr0c = "01234567"[dr0];
|
|
char dr1c = "01234567"[dr1];
|
|
char*dp = result;
|
|
|
|
strcpy(dp, "C8<");
|
|
dp += strlen(dp);
|
|
|
|
for (idx = 0 ; idx < ivl_const_width(cptr) ; idx += 1) {
|
|
switch (bits[ivl_const_width(cptr)-idx-1]) {
|
|
case '0':
|
|
*dp++ = dr0c;
|
|
*dp++ = dr0c;
|
|
*dp++ = '0';
|
|
break;
|
|
case '1':
|
|
*dp++ = dr1c;
|
|
*dp++ = dr1c;
|
|
*dp++ = '1';
|
|
break;
|
|
case 'x':
|
|
case 'X':
|
|
*dp++ = dr0c;
|
|
*dp++ = dr1c;
|
|
*dp++ = 'x';
|
|
break;
|
|
case 'z':
|
|
case 'Z':
|
|
*dp++ = '0';
|
|
*dp++ = '0';
|
|
*dp++ = 'z';
|
|
break;
|
|
default:
|
|
assert(0);
|
|
break;
|
|
}
|
|
assert(dp - result < nresult);
|
|
}
|
|
|
|
strcpy(dp, ">");
|
|
return result;
|
|
}
|
|
|
|
static char* draw_Cr_to_string(double value)
|
|
{
|
|
char tmp[256];
|
|
|
|
uint64_t mant = 0;
|
|
|
|
if (isinf(value)) {
|
|
if (value > 0)
|
|
snprintf(tmp, sizeof(tmp), "Cr<m0g3fff>");
|
|
else
|
|
snprintf(tmp, sizeof(tmp), "Cr<m0g7fff>");
|
|
return strdup(tmp);
|
|
}
|
|
|
|
int sign = 0;
|
|
if (value < 0) {
|
|
sign = 0x4000;
|
|
value *= -1;
|
|
}
|
|
|
|
int expo;
|
|
double fract = frexp(value, &expo);
|
|
fract = ldexp(fract, 63);
|
|
mant = fract;
|
|
expo -= 63;
|
|
|
|
int vexp = expo + 0x1000;
|
|
assert(vexp >= 0);
|
|
assert(vexp < 0x2000);
|
|
vexp += sign;
|
|
|
|
snprintf(tmp, sizeof(tmp), "Cr<m%" PRIx64 "g%x>", mant, vexp);
|
|
return strdup(tmp);
|
|
}
|
|
|
|
/*
|
|
* This function takes a nexus and looks for an input functor. It then
|
|
* draws to the output a string that represents that functor. What we
|
|
* are trying to do here is find the input to the net that is attached
|
|
* to this nexus.
|
|
*/
|
|
|
|
static char* draw_net_input_drive(ivl_nexus_t nex, ivl_nexus_ptr_t nptr)
|
|
{
|
|
unsigned nptr_pin = ivl_nexus_ptr_pin(nptr);
|
|
ivl_net_const_t cptr;
|
|
ivl_net_logic_t lptr;
|
|
ivl_signal_t sptr;
|
|
ivl_lpm_t lpm;
|
|
|
|
lptr = ivl_nexus_ptr_log(nptr);
|
|
if (lptr && (ivl_logic_type(lptr) == IVL_LO_BUFZ) && (nptr_pin == 0))
|
|
do {
|
|
if (! can_elide_bufz(lptr, nptr))
|
|
break;
|
|
|
|
return strdup(draw_net_input(ivl_logic_pin(lptr, 1)));
|
|
} while(0);
|
|
|
|
/* If this is a pulldown device, then there is a single pin
|
|
that drives a constant value to the entire width of the
|
|
vector. The driver normally drives a pull0 value, so a C8<>
|
|
constant is appropriate, but if the drive is really strong,
|
|
then we can draw a C4<> constant instead. */
|
|
if (lptr && (ivl_logic_type(lptr) == IVL_LO_PULLDOWN)) {
|
|
if (ivl_nexus_ptr_drive0(nptr) == IVL_DR_STRONG) {
|
|
size_t result_len = ivl_logic_width(lptr) + 5;
|
|
char*result = malloc(result_len);
|
|
char*dp = result;
|
|
strcpy(dp, "C4<");
|
|
dp += strlen(dp);
|
|
str_repeat(dp, "0", ivl_logic_width(lptr));
|
|
dp += ivl_logic_width(lptr);
|
|
*dp++ = '>';
|
|
*dp = 0;
|
|
assert((dp-result) <= result_len);
|
|
return result;
|
|
} else {
|
|
char val[4];
|
|
size_t result_len = 3*ivl_logic_width(lptr) + 5;
|
|
char*result = malloc(result_len);
|
|
char*dp = result;
|
|
|
|
val[0] = "01234567"[ivl_nexus_ptr_drive0(nptr)];
|
|
val[1] = val[0];
|
|
val[2] = '0';
|
|
val[3] = 0;
|
|
|
|
strcpy(dp, "C8<");
|
|
dp += strlen(dp);
|
|
str_repeat(dp, val, ivl_logic_width(lptr));
|
|
dp += 3*ivl_logic_width(lptr);
|
|
*dp++ = '>';
|
|
*dp = 0;
|
|
assert((dp-result) <= result_len);
|
|
return result;
|
|
}
|
|
}
|
|
|
|
if (lptr && (ivl_logic_type(lptr) == IVL_LO_PULLUP)) {
|
|
if (ivl_nexus_ptr_drive1(nptr) == IVL_DR_STRONG) {
|
|
size_t result_len = 5 + ivl_logic_width(lptr);
|
|
char*result = malloc(result_len);
|
|
char*dp = result;
|
|
strcpy(dp, "C4<");
|
|
dp += strlen(dp);
|
|
str_repeat(dp, "1", ivl_logic_width(lptr));
|
|
dp += ivl_logic_width(lptr);
|
|
*dp++ = '>';
|
|
*dp = 0;
|
|
assert((dp-result) <= result_len);
|
|
return result;
|
|
} else {
|
|
char val[4];
|
|
size_t result_len = 5 + 3*ivl_logic_width(lptr);
|
|
char*result = malloc(result_len);
|
|
char*dp = result;
|
|
|
|
val[0] = "01234567"[ivl_nexus_ptr_drive0(nptr)];
|
|
val[1] = val[0];
|
|
val[2] = '1';
|
|
val[3] = 0;
|
|
|
|
strcpy(dp, "C8<");
|
|
dp += strlen(dp);
|
|
str_repeat(dp, val, ivl_logic_width(lptr));
|
|
dp += 3*ivl_logic_width(lptr);
|
|
*dp++ = '>';
|
|
*dp = 0;
|
|
assert((dp-result) <= result_len);
|
|
return result;
|
|
}
|
|
}
|
|
|
|
if (lptr && (nptr_pin == 0)) {
|
|
char tmp[128];
|
|
snprintf(tmp, sizeof tmp, "L_%p", lptr);
|
|
return strdup(tmp);
|
|
}
|
|
|
|
sptr = ivl_nexus_ptr_sig(nptr);
|
|
if (sptr && (ivl_signal_type(sptr) == IVL_SIT_REG)) {
|
|
char tmp[128];
|
|
/* Input is a .var. This device may be a non-zero pin
|
|
because it may be an array of reg vectors. */
|
|
snprintf(tmp, sizeof tmp, "v%p_%u", sptr, nptr_pin);
|
|
return strdup(tmp);
|
|
}
|
|
|
|
cptr = ivl_nexus_ptr_con(nptr);
|
|
if (cptr) {
|
|
/* Constants should have exactly 1 pin, with a literal value. */
|
|
assert(nptr_pin == 0);
|
|
char *result = 0;
|
|
|
|
switch (ivl_const_type(cptr)) {
|
|
case IVL_VT_LOGIC:
|
|
case IVL_VT_BOOL:
|
|
if ((ivl_nexus_ptr_drive0(nptr) == IVL_DR_STRONG)
|
|
&& (ivl_nexus_ptr_drive1(nptr) == IVL_DR_STRONG)) {
|
|
|
|
result = draw_C4_to_string(cptr);
|
|
|
|
} else {
|
|
result = draw_C8_to_string(cptr,
|
|
ivl_nexus_ptr_drive0(nptr),
|
|
ivl_nexus_ptr_drive1(nptr));
|
|
}
|
|
break;
|
|
|
|
case IVL_VT_REAL:
|
|
result = draw_Cr_to_string(ivl_const_real(cptr));
|
|
break;
|
|
|
|
default:
|
|
assert(0);
|
|
break;
|
|
}
|
|
|
|
ivl_expr_t d_rise = ivl_const_delay(cptr, 0);
|
|
ivl_expr_t d_fall = ivl_const_delay(cptr, 1);
|
|
ivl_expr_t d_decay = ivl_const_delay(cptr, 2);
|
|
|
|
/* We have a delayed constant, so we need to build some code. */
|
|
if (d_rise != 0) {
|
|
assert(number_is_immediate(d_rise, 64));
|
|
assert(number_is_immediate(d_fall, 64));
|
|
assert(number_is_immediate(d_decay, 64));
|
|
|
|
fprintf(vvp_out, "L_%p/d .functor BUFZ 1, %s, "
|
|
"C4<0>, C4<0>, C4<0>;\n", cptr, result);
|
|
|
|
fprintf(vvp_out, "L_%p .delay (%lu,%lu,%lu) L_%p/d;\n",
|
|
cptr, get_number_immediate(d_rise),
|
|
get_number_immediate(d_rise),
|
|
get_number_immediate(d_rise), cptr);
|
|
|
|
free(result);
|
|
char tmp[128];
|
|
snprintf(tmp, sizeof tmp, "L_%p", cptr);
|
|
result = strdup(tmp);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
lpm = ivl_nexus_ptr_lpm(nptr);
|
|
if (lpm) switch (ivl_lpm_type(lpm)) {
|
|
|
|
case IVL_LPM_FF:
|
|
case IVL_LPM_ADD:
|
|
case IVL_LPM_ARRAY:
|
|
case IVL_LPM_CONCAT:
|
|
case IVL_LPM_CMP_EEQ:
|
|
case IVL_LPM_CMP_EQ:
|
|
case IVL_LPM_CMP_GE:
|
|
case IVL_LPM_CMP_GT:
|
|
case IVL_LPM_CMP_NE:
|
|
case IVL_LPM_CMP_NEE:
|
|
case IVL_LPM_RE_AND:
|
|
case IVL_LPM_RE_OR:
|
|
case IVL_LPM_RE_XOR:
|
|
case IVL_LPM_RE_NAND:
|
|
case IVL_LPM_RE_NOR:
|
|
case IVL_LPM_RE_XNOR:
|
|
case IVL_LPM_SFUNC:
|
|
case IVL_LPM_SHIFTL:
|
|
case IVL_LPM_SHIFTR:
|
|
case IVL_LPM_SIGN_EXT:
|
|
case IVL_LPM_SUB:
|
|
case IVL_LPM_MULT:
|
|
case IVL_LPM_MUX:
|
|
case IVL_LPM_POW:
|
|
case IVL_LPM_DIVIDE:
|
|
case IVL_LPM_MOD:
|
|
case IVL_LPM_UFUNC:
|
|
case IVL_LPM_PART_VP:
|
|
case IVL_LPM_PART_PV: /* NOTE: This is only a partial driver. */
|
|
case IVL_LPM_REPEAT:
|
|
if (ivl_lpm_q(lpm, 0) == nex) {
|
|
char tmp[128];
|
|
snprintf(tmp, sizeof tmp, "L_%p", lpm);
|
|
return strdup(tmp);
|
|
}
|
|
break;
|
|
|
|
case IVL_LPM_PART_BI:
|
|
if (ivl_lpm_q(lpm, 0) == nex) {
|
|
char tmp[128];
|
|
snprintf(tmp, sizeof tmp, "L_%p/P", lpm);
|
|
return strdup(tmp);
|
|
} else if (ivl_lpm_data(lpm,0) == nex) {
|
|
char tmp[128];
|
|
snprintf(tmp, sizeof tmp, "L_%p/V", lpm);
|
|
return strdup(tmp);
|
|
}
|
|
break;
|
|
}
|
|
|
|
fprintf(stderr, "internal error: no input to nexus %s\n",
|
|
ivl_nexus_name(nex));
|
|
assert(0);
|
|
return strdup("C<z>");
|
|
}
|
|
|
|
static int nexus_drive_is_strength_aware(ivl_nexus_ptr_t nptr)
|
|
{
|
|
if (ivl_nexus_ptr_drive0(nptr) != IVL_DR_STRONG)
|
|
return 1;
|
|
if (ivl_nexus_ptr_drive1(nptr) != IVL_DR_STRONG)
|
|
return 1;
|
|
|
|
ivl_net_logic_t log = ivl_nexus_ptr_log(nptr);
|
|
if (log != 0) {
|
|
/* These logic gates are able to generate unusual
|
|
strength values and so their outputs are considered
|
|
strength aware. */
|
|
if (ivl_logic_type(log) == IVL_LO_BUFIF0)
|
|
return 1;
|
|
if (ivl_logic_type(log) == IVL_LO_BUFIF1)
|
|
return 1;
|
|
if (ivl_logic_type(log) == IVL_LO_PMOS)
|
|
return 1;
|
|
if (ivl_logic_type(log) == IVL_LO_NMOS)
|
|
return 1;
|
|
if (ivl_logic_type(log) == IVL_LO_CMOS)
|
|
return 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* This function draws the input to a net into a string. What that
|
|
* means is that it returns a static string that can be used to
|
|
* represent a resolved driver to a nexus. If there are multiple
|
|
* drivers to the nexus, then it writes out the resolver declarations
|
|
* needed to perform strength resolution.
|
|
*
|
|
* The string that this returns is malloced, and that means that the
|
|
* caller must free the string or store it permanently. This function
|
|
* does *not* check for a previously calculated string. Use the
|
|
* draw_net_input for the general case.
|
|
*/
|
|
/* Omit LPMPART_BI device pin-data(0) drivers. */
|
|
# define OMIT_PART_BI_DATA 0x0001
|
|
|
|
static char* draw_net_input_x(ivl_nexus_t nex,
|
|
ivl_nexus_ptr_t omit_ptr, int omit_flags,
|
|
struct vvp_nexus_data*nex_data)
|
|
{
|
|
ivl_signal_type_t res;
|
|
char result[512];
|
|
unsigned idx;
|
|
int level;
|
|
unsigned ndrivers = 0;
|
|
static ivl_nexus_ptr_t *drivers = 0x0;
|
|
static unsigned adrivers = 0;
|
|
|
|
const char*resolv_type;
|
|
|
|
char*nex_private = 0;
|
|
|
|
/* Accumulate nex_data flags. */
|
|
int nex_flags = 0;
|
|
|
|
res = signal_type_of_nexus(nex);
|
|
switch (res) {
|
|
case IVL_SIT_TRI:
|
|
resolv_type = "tri";
|
|
break;
|
|
case IVL_SIT_TRI0:
|
|
resolv_type = "tri0";
|
|
nex_flags |= VVP_NEXUS_DATA_STR;
|
|
break;
|
|
case IVL_SIT_TRI1:
|
|
resolv_type = "tri1";
|
|
nex_flags |= VVP_NEXUS_DATA_STR;
|
|
break;
|
|
case IVL_SIT_TRIAND:
|
|
resolv_type = "triand";
|
|
break;
|
|
case IVL_SIT_TRIOR:
|
|
resolv_type = "trior";
|
|
break;
|
|
default:
|
|
fprintf(stderr, "vvp.tgt: Unsupported signal type: %u\n", res);
|
|
assert(0);
|
|
resolv_type = "tri";
|
|
break;
|
|
}
|
|
|
|
|
|
for (idx = 0 ; idx < ivl_nexus_ptrs(nex) ; idx += 1) {
|
|
ivl_lpm_t lpm_tmp;
|
|
ivl_nexus_ptr_t nptr = ivl_nexus_ptr(nex, idx);
|
|
|
|
/* If we are supposed to skip LPM_PART_BI data pins,
|
|
check that this driver is that. */
|
|
if ((omit_flags&OMIT_PART_BI_DATA)
|
|
&& (lpm_tmp = ivl_nexus_ptr_lpm(nptr))
|
|
&& (nex == ivl_lpm_data(lpm_tmp,0)))
|
|
continue;
|
|
|
|
if (nptr == omit_ptr)
|
|
continue;
|
|
|
|
/* Skip input only pins. */
|
|
if ((ivl_nexus_ptr_drive0(nptr) == IVL_DR_HiZ)
|
|
&& (ivl_nexus_ptr_drive1(nptr) == IVL_DR_HiZ))
|
|
continue;
|
|
|
|
/* Mark the strength-aware flag if the driver can
|
|
generate values other than the standard "6"
|
|
strength. */
|
|
if (nexus_drive_is_strength_aware(nptr))
|
|
nex_flags |= VVP_NEXUS_DATA_STR;
|
|
|
|
/* Save this driver. */
|
|
if (ndrivers >= adrivers) {
|
|
adrivers += 4;
|
|
drivers = realloc(drivers, adrivers*sizeof(ivl_nexus_ptr_t));
|
|
assert(drivers);
|
|
}
|
|
drivers[ndrivers] = nptr;
|
|
ndrivers += 1;
|
|
}
|
|
|
|
/* If the caller is collecting nexus information, then save
|
|
the nexus driver count in the nex_data. */
|
|
if (nex_data) {
|
|
nex_data->drivers_count = ndrivers;
|
|
nex_data->flags |= nex_flags;
|
|
}
|
|
|
|
/* If the nexus has no drivers, then send a constant HiZ into
|
|
the net. */
|
|
if (ndrivers == 0) {
|
|
unsigned idx, wid = width_of_nexus(nex);
|
|
char*tmp = malloc(wid + 5);
|
|
nex_private = tmp;
|
|
strcpy(tmp, "C4<");
|
|
tmp += strlen(tmp);
|
|
switch (res) {
|
|
case IVL_SIT_TRI:
|
|
for (idx = 0 ; idx < wid ; idx += 1)
|
|
*tmp++ = 'z';
|
|
break;
|
|
case IVL_SIT_TRI0:
|
|
for (idx = 0 ; idx < wid ; idx += 1)
|
|
*tmp++ = '0';
|
|
break;
|
|
case IVL_SIT_TRI1:
|
|
for (idx = 0 ; idx < wid ; idx += 1)
|
|
*tmp++ = '1';
|
|
break;
|
|
default:
|
|
assert(0);
|
|
}
|
|
*tmp++ = '>';
|
|
*tmp = 0;
|
|
return nex_private;
|
|
}
|
|
|
|
|
|
/* If the nexus has exactly one driver, then simply draw
|
|
it. Note that this will *not* work if the nexus is not a
|
|
TRI type nexus. */
|
|
if (ndrivers == 1 && res == IVL_SIT_TRI) {
|
|
ivl_signal_t path_sig = find_modpath(nex);
|
|
if (path_sig) {
|
|
char*nex_str = draw_net_input_drive(nex, drivers[0]);
|
|
char modpath_label[64];
|
|
snprintf(modpath_label, sizeof modpath_label,
|
|
"V_%p/m", path_sig);
|
|
nex_private = strdup(modpath_label);
|
|
draw_modpath(path_sig, nex_str);
|
|
|
|
} else {
|
|
nex_private = draw_net_input_drive(nex, drivers[0]);
|
|
}
|
|
return nex_private;
|
|
}
|
|
|
|
level = 0;
|
|
while (ndrivers) {
|
|
unsigned int inst;
|
|
for (inst = 0; inst < ndrivers; inst += 4) {
|
|
if (ndrivers > 4)
|
|
fprintf(vvp_out, "RS_%p/%d/%d .resolv tri",
|
|
nex, level, inst);
|
|
else
|
|
fprintf(vvp_out, "RS_%p .resolv %s",
|
|
nex, resolv_type);
|
|
|
|
for (idx = inst; idx < ndrivers && idx < inst+4; idx += 1) {
|
|
if (level) {
|
|
fprintf(vvp_out, ", RS_%p/%d/%d",
|
|
nex, level - 1, idx*4);
|
|
} else {
|
|
char*drive = draw_net_input_drive(nex, drivers[idx]);
|
|
fprintf(vvp_out, ", %s", drive);
|
|
free(drive);
|
|
}
|
|
}
|
|
for ( ; idx < inst+4 ; idx += 1) {
|
|
fprintf(vvp_out, ", ");
|
|
draw_C4_repeated_constant('z',width_of_nexus(nex));
|
|
}
|
|
|
|
fprintf(vvp_out, ";\n");
|
|
}
|
|
if (ndrivers > 4)
|
|
ndrivers = (ndrivers+3) / 4;
|
|
else
|
|
ndrivers = 0;
|
|
level += 1;
|
|
}
|
|
|
|
sprintf(result, "RS_%p", nex);
|
|
nex_private = strdup(result);
|
|
return nex_private;
|
|
}
|
|
|
|
/*
|
|
* Get a cached description of the nexus input, or create one if this
|
|
* nexus has not been cached yet. This is a wrapper for the common
|
|
* case call to draw_net_input_x.
|
|
*/
|
|
const char*draw_net_input(ivl_nexus_t nex)
|
|
{
|
|
struct vvp_nexus_data*nex_data = (struct vvp_nexus_data*)
|
|
ivl_nexus_get_private(nex);
|
|
|
|
/* If this nexus already has a label, then its input is
|
|
already figured out. Just return the existing label. */
|
|
if (nex_data && nex_data->net_input)
|
|
return nex_data->net_input;
|
|
|
|
if (nex_data == 0) {
|
|
nex_data = new_nexus_data();
|
|
ivl_nexus_set_private(nex, nex_data);
|
|
}
|
|
|
|
assert(nex_data->net_input == 0);
|
|
nex_data->net_input = draw_net_input_x(nex, 0, 0, nex_data);
|
|
|
|
return nex_data->net_input;
|
|
}
|
|
|
|
const char*draw_input_from_net(ivl_nexus_t nex)
|
|
{
|
|
static char result[32];
|
|
unsigned word;
|
|
|
|
ivl_signal_t sig = signal_of_nexus(nex, &word);
|
|
if (sig == 0)
|
|
return draw_net_input(nex);
|
|
|
|
snprintf(result, sizeof result, "v%p_%u", sig, word);
|
|
return result;
|
|
}
|
|
|
|
|
|
/*
|
|
* This function draws a reg/int/variable in the scope. This is a very
|
|
* simple device to draw as there are no inputs to connect so no need
|
|
* to scan the nexus. We do have to account for the possibility that
|
|
* the device is arrayed, though, by making a node for each array element.
|
|
*/
|
|
static void draw_reg_in_scope(ivl_signal_t sig)
|
|
{
|
|
int msb = ivl_signal_msb(sig);
|
|
int lsb = ivl_signal_lsb(sig);
|
|
|
|
const char*datatype_flag = ivl_signal_integer(sig) ? "/i" :
|
|
ivl_signal_signed(sig)? "/s" : "";
|
|
const char*local_flag = ivl_signal_local(sig)? "*" : "";
|
|
|
|
switch (ivl_signal_data_type(sig)) {
|
|
case IVL_VT_REAL:
|
|
datatype_flag = "/real";
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
/* If the reg objects are collected into an array, then first
|
|
write out the .array record to declare the array indices. */
|
|
if (ivl_signal_dimensions(sig) > 0) {
|
|
unsigned word_count = ivl_signal_array_count(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, %d %d;\n",
|
|
sig, datatype_flag,
|
|
vvp_mangle_name(ivl_signal_basename(sig)),
|
|
last, first, msb, lsb);
|
|
|
|
} else {
|
|
|
|
fprintf(vvp_out, "v%p_0 .var%s %s\"%s\", %d %d;%s\n",
|
|
sig, datatype_flag, local_flag,
|
|
vvp_mangle_name(ivl_signal_basename(sig)), msb, lsb,
|
|
ivl_signal_local(sig)? " Local signal" : "");
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
* This function draws a net. This is a bit more complicated as we
|
|
* have to find an appropriate functor to connect to the input.
|
|
*/
|
|
static void draw_net_in_scope(ivl_signal_t sig)
|
|
{
|
|
int msb = ivl_signal_msb(sig);
|
|
int lsb = ivl_signal_lsb(sig);
|
|
|
|
const char*datatype_flag = ivl_signal_signed(sig)? "/s" : "";
|
|
const char*local_flag = ivl_signal_local(sig)? "*" : "";
|
|
unsigned iword;
|
|
|
|
switch (ivl_signal_data_type(sig)) {
|
|
case IVL_VT_REAL:
|
|
datatype_flag = "/real";
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
for (iword = 0 ; iword < ivl_signal_array_count(sig); iword += 1) {
|
|
|
|
unsigned word_count = ivl_signal_array_count(sig);
|
|
unsigned dimensions = ivl_signal_dimensions(sig);
|
|
struct vvp_nexus_data*nex_data;
|
|
|
|
/* Connect the pin of the signal to something. */
|
|
ivl_nexus_t nex = ivl_signal_nex(sig, iword);
|
|
const char*driver = draw_net_input(nex);
|
|
|
|
nex_data = (struct vvp_nexus_data*)ivl_nexus_get_private(nex);
|
|
assert(nex_data);
|
|
|
|
if (nex_data->net == 0) {
|
|
int strength_aware_flag = 0;
|
|
const char*vec8 = "";
|
|
if (nex_data->flags&VVP_NEXUS_DATA_STR)
|
|
strength_aware_flag = 1;
|
|
if (nex_data->drivers_count > 1)
|
|
vec8 = "8";
|
|
if (strength_aware_flag)
|
|
vec8 = "8";
|
|
|
|
if (iword == 0 && dimensions > 0) {
|
|
int last = ivl_signal_array_base(sig) + word_count-1;
|
|
int first = ivl_signal_array_base(sig);
|
|
fprintf(vvp_out, "v%p .array \"%s\", %d %d;\n",
|
|
sig, vvp_mangle_name(ivl_signal_basename(sig)),
|
|
last, first);
|
|
}
|
|
if (dimensions > 0) {
|
|
/* If this is a word of an array, then use an
|
|
array reference in place of the net name. */
|
|
fprintf(vvp_out, "v%p_%u .net%s%s v%p %u, %d %d, %s;"
|
|
" %u drivers%s\n",
|
|
sig, iword, vec8, datatype_flag, sig,
|
|
iword, msb, lsb, driver,
|
|
nex_data->drivers_count,
|
|
strength_aware_flag?", strength-aware":"");
|
|
} else {
|
|
/* If this is an isolated word, it uses its
|
|
own name. */
|
|
assert(word_count == 1);
|
|
fprintf(vvp_out, "v%p_%u .net%s%s %s\"%s\", %d %d, %s;"
|
|
" %u drivers%s\n",
|
|
sig, iword, vec8, datatype_flag, local_flag,
|
|
vvp_mangle_name(ivl_signal_basename(sig)),
|
|
msb, lsb, driver,
|
|
nex_data->drivers_count,
|
|
strength_aware_flag?", strength-aware":"");
|
|
}
|
|
nex_data->net = sig;
|
|
nex_data->net_word = iword;
|
|
|
|
} else if (dimensions > 0) {
|
|
|
|
/* In this case, we have an alias to an existing
|
|
signal array. this typically is an instance of
|
|
port collapsing that the elaborator combined to
|
|
discover that the entire array can be collapsed,
|
|
so the word count for the signal and the alias
|
|
*must* match. */
|
|
|
|
if (word_count == ivl_signal_array_count(nex_data->net)) {
|
|
if (iword == 0) {
|
|
fprintf(vvp_out, "v%p .array \"%s\", v%p; Alias to %s\n",
|
|
sig, vvp_mangle_name(ivl_signal_basename(sig)),
|
|
nex_data->net,
|
|
ivl_signal_basename(nex_data->net));
|
|
}
|
|
/* An alias for an individual word. */
|
|
} else {
|
|
if (iword == 0) {
|
|
int first = ivl_signal_array_base(sig);
|
|
int last = first + word_count-1;
|
|
fprintf(vvp_out, "v%p .array \"%s\", %d %d;\n",
|
|
sig,
|
|
vvp_mangle_name(ivl_signal_basename(sig)),
|
|
last, first);
|
|
}
|
|
|
|
fprintf(vvp_out, "v%p_%u .alias%s v%p %u, %d %d, "
|
|
"v%p_%u; Alias to %s\n", sig, iword,
|
|
datatype_flag, sig, iword, msb, lsb,
|
|
nex_data->net, nex_data->net_word,
|
|
ivl_signal_basename(nex_data->net));
|
|
}
|
|
} else {
|
|
/* Finally, we may have an alias that is a word
|
|
connected to another word. Again, this is a
|
|
case of port collapsing. */
|
|
|
|
/* For the alias, create a different kind of node
|
|
that refers to the alias source data instead of
|
|
holding our own data. */
|
|
fprintf(vvp_out, "v%p_%u .alias%s \"%s\", %d %d, v%p_%u;\n",
|
|
sig, iword, datatype_flag,
|
|
vvp_mangle_name(ivl_signal_basename(sig)),
|
|
msb, lsb, nex_data->net, nex_data->net_word);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void draw_delay(ivl_net_logic_t lptr)
|
|
{
|
|
ivl_expr_t d0 = ivl_logic_delay(lptr, 0);
|
|
ivl_expr_t d1 = ivl_logic_delay(lptr, 1);
|
|
ivl_expr_t d2 = ivl_logic_delay(lptr, 2);
|
|
|
|
if (d0 == 0 && d1 == 0 && d2 == 0)
|
|
return;
|
|
|
|
/* FIXME: Assume that the expression is a constant */
|
|
assert(number_is_immediate(d0, 64));
|
|
assert(number_is_immediate(d1, 64));
|
|
assert(number_is_immediate(d2, 64));
|
|
|
|
if (d0 == d1 && d1 == d2)
|
|
fprintf(vvp_out, " (%lu)", get_number_immediate(d0));
|
|
else
|
|
fprintf(vvp_out, " (%lu,%lu,%lu)",
|
|
get_number_immediate(d0),
|
|
get_number_immediate(d1),
|
|
get_number_immediate(d2));
|
|
}
|
|
|
|
static void draw_udp_def(ivl_udp_t udp)
|
|
{
|
|
unsigned init;
|
|
unsigned i;
|
|
|
|
switch (ivl_udp_init(udp))
|
|
{
|
|
case '0':
|
|
init = 0;
|
|
break;
|
|
case '1':
|
|
init = 1;
|
|
break;
|
|
default:
|
|
init = 2;
|
|
break;
|
|
}
|
|
|
|
if (ivl_udp_sequ(udp))
|
|
fprintf(vvp_out,
|
|
"UDP_%s .udp/sequ \"%s\", %d, %d",
|
|
vvp_mangle_id(ivl_udp_name(udp)),
|
|
vvp_mangle_name(ivl_udp_name(udp)),
|
|
ivl_udp_nin(udp),
|
|
init );
|
|
else
|
|
fprintf(vvp_out,
|
|
"UDP_%s .udp/comb \"%s\", %d",
|
|
vvp_mangle_id(ivl_udp_name(udp)),
|
|
vvp_mangle_name(ivl_udp_name(udp)),
|
|
ivl_udp_nin(udp));
|
|
|
|
for (i=0; i<ivl_udp_rows(udp); i++)
|
|
fprintf(vvp_out, "\n ,\"%s\"", ivl_udp_row(udp, i) );
|
|
|
|
fprintf(vvp_out, ";\n");
|
|
}
|
|
|
|
static void draw_udp_in_scope(ivl_net_logic_t lptr)
|
|
{
|
|
unsigned pdx;
|
|
|
|
ivl_udp_t udp = ivl_logic_udp(lptr);
|
|
|
|
static ivl_udp_t *udps = 0x0;
|
|
static int nudps = 0;
|
|
int i;
|
|
|
|
for (i=0; i<nudps; i++)
|
|
if (udps[i] == udp)
|
|
break;
|
|
|
|
if (i >= nudps)
|
|
{
|
|
udps = realloc(udps, (nudps+1)*sizeof(ivl_udp_t));
|
|
assert(udps);
|
|
udps[nudps++] = udp;
|
|
draw_udp_def(udp);
|
|
}
|
|
|
|
fprintf(vvp_out, "L_%p .udp", lptr);
|
|
fprintf(vvp_out, " UDP_%s",
|
|
vvp_mangle_id(ivl_udp_name(udp)));
|
|
draw_delay(lptr);
|
|
|
|
for (pdx = 1 ; pdx < ivl_logic_pins(lptr) ; pdx += 1) {
|
|
ivl_nexus_t nex = ivl_logic_pin(lptr, pdx);
|
|
|
|
/* Unlike other logic gates, primitives may have unconnected
|
|
inputs. The proper behavior is to attach a HiZ to the
|
|
port. */
|
|
if (nex == 0) {
|
|
assert(ivl_logic_width(lptr) == 1);
|
|
fprintf(vvp_out, ", C4<z>");
|
|
|
|
} else {
|
|
fprintf(vvp_out, ", %s", draw_net_input(nex));
|
|
}
|
|
}
|
|
|
|
fprintf(vvp_out, ";\n");
|
|
}
|
|
|
|
static void draw_logic_in_scope(ivl_net_logic_t lptr)
|
|
{
|
|
unsigned pdx;
|
|
const char*ltype = "?";
|
|
const char*lcasc = 0;
|
|
char identity_val = '0';
|
|
|
|
int need_delay_flag = ivl_logic_delay(lptr,0)? 1 : 0;
|
|
|
|
unsigned vector_width = width_of_nexus(ivl_logic_pin(lptr, 0));
|
|
|
|
ivl_drive_t str0, str1;
|
|
|
|
int level;
|
|
int ninp = ivl_logic_pins(lptr) - 1;
|
|
typedef const char*const_charp;
|
|
const_charp*input_strings = calloc(ninp, sizeof(const_charp));
|
|
|
|
for (pdx = 0 ; pdx < ninp ; pdx += 1) {
|
|
ivl_nexus_t nex = ivl_logic_pin(lptr, pdx+1);
|
|
if (nex == 0) {
|
|
/* Only UDPs can have unconnected inputs. */
|
|
assert(ivl_logic_type(lptr) == IVL_LO_UDP);
|
|
input_strings[pdx] = 0;
|
|
} else {
|
|
input_strings[pdx] = draw_net_input(nex);
|
|
}
|
|
}
|
|
|
|
switch (ivl_logic_type(lptr)) {
|
|
|
|
case IVL_LO_UDP:
|
|
free(input_strings);
|
|
draw_udp_in_scope(lptr);
|
|
return;
|
|
|
|
case IVL_LO_BUFZ: {
|
|
/* Draw bufz objects, but only if the gate cannot
|
|
be elided. If I can elide it, then the
|
|
draw_nex_input will take care of it for me. */
|
|
ivl_nexus_ptr_t nptr = ivl_logic_pin_ptr(lptr,0);
|
|
|
|
ltype = "BUFZ";
|
|
|
|
if (can_elide_bufz(lptr, nptr))
|
|
return;
|
|
|
|
break;
|
|
}
|
|
|
|
case IVL_LO_PULLDOWN:
|
|
case IVL_LO_PULLUP:
|
|
/* Skip pullup and pulldown objects. Things that have
|
|
pull objects as inputs will instead generate the
|
|
appropriate C<?> symbol. */
|
|
free(input_strings);
|
|
return;
|
|
|
|
case IVL_LO_AND:
|
|
ltype = "AND";
|
|
identity_val = '1';
|
|
break;
|
|
|
|
case IVL_LO_BUF:
|
|
ltype = "BUF";
|
|
break;
|
|
|
|
case IVL_LO_BUFIF0:
|
|
ltype = "BUFIF0";
|
|
break;
|
|
|
|
case IVL_LO_BUFIF1:
|
|
ltype = "BUFIF1";
|
|
break;
|
|
|
|
case IVL_LO_NAND:
|
|
ltype = "NAND";
|
|
lcasc = "AND";
|
|
identity_val = '1';
|
|
break;
|
|
|
|
case IVL_LO_NOR:
|
|
ltype = "NOR";
|
|
lcasc = "OR";
|
|
break;
|
|
|
|
case IVL_LO_NOT:
|
|
ltype = "NOT";
|
|
break;
|
|
|
|
case IVL_LO_OR:
|
|
ltype = "OR";
|
|
break;
|
|
|
|
case IVL_LO_XNOR:
|
|
ltype = "XNOR";
|
|
lcasc = "XOR";
|
|
break;
|
|
|
|
case IVL_LO_XOR:
|
|
ltype = "XOR";
|
|
break;
|
|
|
|
case IVL_LO_CMOS:
|
|
ltype = "CMOS";
|
|
break;
|
|
|
|
case IVL_LO_PMOS:
|
|
ltype = "PMOS";
|
|
break;
|
|
|
|
case IVL_LO_NMOS:
|
|
ltype = "NMOS";
|
|
break;
|
|
|
|
case IVL_LO_RCMOS:
|
|
ltype = "RCMOS";
|
|
break;
|
|
|
|
case IVL_LO_RPMOS:
|
|
ltype = "RPMOS";
|
|
break;
|
|
|
|
case IVL_LO_RNMOS:
|
|
ltype = "RNMOS";
|
|
break;
|
|
|
|
case IVL_LO_NOTIF0:
|
|
ltype = "NOTIF0";
|
|
break;
|
|
|
|
case IVL_LO_NOTIF1:
|
|
ltype = "NOTIF1";
|
|
break;
|
|
|
|
default:
|
|
fprintf(stderr, "vvp.tgt: error: Unhandled logic type: %u\n",
|
|
ivl_logic_type(lptr));
|
|
ltype = "?";
|
|
break;
|
|
}
|
|
|
|
{ ivl_nexus_t nex = ivl_logic_pin(lptr, 0);
|
|
ivl_nexus_ptr_t nptr = 0;
|
|
unsigned idx;
|
|
for (idx = 0 ; idx < ivl_nexus_ptrs(nex) ; idx += 1) {
|
|
nptr = ivl_nexus_ptr(nex,idx);
|
|
if (ivl_nexus_ptr_log(nptr) != lptr)
|
|
continue;
|
|
if (ivl_nexus_ptr_pin(nptr) != 0)
|
|
continue;
|
|
break;
|
|
}
|
|
str0 = ivl_nexus_ptr_drive0(nptr);
|
|
str1 = ivl_nexus_ptr_drive1(nptr);
|
|
}
|
|
|
|
if (!lcasc)
|
|
lcasc = ltype;
|
|
|
|
/* Get all the input label that I will use for parameters to
|
|
the functor that I create later. */
|
|
ninp = ivl_logic_pins(lptr) - 1;
|
|
input_strings = calloc(ninp, sizeof(char*));
|
|
for (pdx = 0 ; pdx < ninp ; pdx += 1)
|
|
input_strings[pdx] = draw_net_input(ivl_logic_pin(lptr, pdx+1));
|
|
|
|
level = 0;
|
|
ninp = ivl_logic_pins(lptr) - 1;
|
|
while (ninp) {
|
|
int inst;
|
|
for (inst = 0; inst < ninp; inst += 4) {
|
|
if (ninp > 4)
|
|
fprintf(vvp_out, "L_%p/%d/%d .functor %s %u",
|
|
lptr, level, inst, lcasc, vector_width);
|
|
else {
|
|
fprintf(vvp_out, "L_%p%s .functor %s %u",
|
|
lptr, need_delay_flag? "/d" : "",
|
|
ltype, vector_width);
|
|
|
|
if (str0 != IVL_DR_STRONG || str1 != IVL_DR_STRONG)
|
|
fprintf(vvp_out, " [%u %u]", str0, str1);
|
|
|
|
}
|
|
for (pdx = inst; pdx < ninp && pdx < inst+4 ; pdx += 1) {
|
|
if (level) {
|
|
fprintf(vvp_out, ", L_%p/%d/%d",
|
|
lptr, level - 1, pdx*4);
|
|
} else {
|
|
fprintf(vvp_out, ", %s", input_strings[pdx]);
|
|
}
|
|
}
|
|
for ( ; pdx < inst+4 ; pdx += 1) {
|
|
unsigned wdx;
|
|
fprintf(vvp_out, ", C4<");
|
|
for (wdx = 0 ; wdx < vector_width ; wdx += 1)
|
|
fprintf(vvp_out, "%c", identity_val);
|
|
fprintf(vvp_out, ">");
|
|
}
|
|
|
|
fprintf(vvp_out, ";\n");
|
|
}
|
|
if (ninp > 4)
|
|
ninp = (ninp+3) / 4;
|
|
else
|
|
ninp = 0;
|
|
level += 1;
|
|
}
|
|
|
|
/* Free the array of char*. The strings themselves are
|
|
persistent, held by the ivl_nexus_t objects. */
|
|
free(input_strings);
|
|
|
|
/* If there are delays, then draw the delay functor to carry
|
|
that delay. This is the final output. */
|
|
if (need_delay_flag) {
|
|
ivl_expr_t rise_exp = ivl_logic_delay(lptr,0);
|
|
ivl_expr_t fall_exp = ivl_logic_delay(lptr,1);
|
|
ivl_expr_t decay_exp = ivl_logic_delay(lptr,2);
|
|
|
|
if (number_is_immediate(rise_exp,64)
|
|
&& number_is_immediate(fall_exp,64)
|
|
&& number_is_immediate(decay_exp,64)) {
|
|
|
|
fprintf(vvp_out, "L_%p .delay (%lu,%lu,%lu) L_%p/d;\n",
|
|
lptr, get_number_immediate(rise_exp),
|
|
get_number_immediate(rise_exp),
|
|
get_number_immediate(rise_exp), lptr);
|
|
} else {
|
|
ivl_signal_t sig;
|
|
assert(ivl_expr_type(rise_exp) == IVL_EX_SIGNAL);
|
|
assert(ivl_expr_type(fall_exp) == IVL_EX_SIGNAL);
|
|
assert(ivl_expr_type(decay_exp) == IVL_EX_SIGNAL);
|
|
|
|
fprintf(vvp_out, "L_%p .delay L_%p/d", lptr, lptr);
|
|
|
|
sig = ivl_expr_signal(rise_exp);
|
|
assert(ivl_signal_array_count(sig) == 1);
|
|
fprintf(vvp_out, ", v%p_0", sig);
|
|
|
|
sig = ivl_expr_signal(fall_exp);
|
|
assert(ivl_signal_array_count(sig) == 1);
|
|
fprintf(vvp_out, ", v%p_0", sig);
|
|
|
|
sig = ivl_expr_signal(decay_exp);
|
|
assert(ivl_signal_array_count(sig) == 1);
|
|
fprintf(vvp_out, ", v%p_0;\n", sig);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void draw_event_in_scope(ivl_event_t obj)
|
|
{
|
|
unsigned nany = ivl_event_nany(obj);
|
|
unsigned nneg = ivl_event_nneg(obj);
|
|
unsigned npos = ivl_event_npos(obj);
|
|
|
|
unsigned cnt = 0;
|
|
|
|
/* Figure out how many probe functors are needed. */
|
|
if (nany > 0)
|
|
cnt += (nany+3) / 4;
|
|
|
|
if (nneg > 0)
|
|
cnt += (nneg+3) / 4;
|
|
|
|
if (npos > 0)
|
|
cnt += (npos+3) / 4;
|
|
|
|
if (cnt == 0) {
|
|
/* If none are needed, then this is a named event. The
|
|
code needed is easy. */
|
|
fprintf(vvp_out, "E_%p .event \"%s\";\n", obj,
|
|
vvp_mangle_name(ivl_event_basename(obj)));
|
|
|
|
} else if (cnt > 1) {
|
|
/* There are a bunch of events that need to be event/or
|
|
combined. */
|
|
unsigned idx;
|
|
unsigned ecnt = 0;
|
|
|
|
for (idx = 0 ; idx < nany ; idx += 4, ecnt += 1) {
|
|
unsigned sub, top;
|
|
|
|
fprintf(vvp_out, "E_%p/%u .event edge", obj, ecnt);
|
|
|
|
top = idx + 4;
|
|
if (nany < top)
|
|
top = nany;
|
|
for (sub = idx ; sub < top ; sub += 1) {
|
|
ivl_nexus_t nex = ivl_event_any(obj, sub);
|
|
fprintf(vvp_out, ", %s", draw_input_from_net(nex));
|
|
}
|
|
fprintf(vvp_out, ";\n");
|
|
}
|
|
|
|
for (idx = 0 ; idx < nneg ; idx += 4, ecnt += 1) {
|
|
unsigned sub, top;
|
|
|
|
fprintf(vvp_out, "E_%p/%u .event negedge", obj, ecnt);
|
|
|
|
top = idx + 4;
|
|
if (nneg < top)
|
|
top = nneg;
|
|
for (sub = idx ; sub < top ; sub += 1) {
|
|
ivl_nexus_t nex = ivl_event_neg(obj, sub);
|
|
fprintf(vvp_out, ", %s", draw_input_from_net(nex));
|
|
}
|
|
fprintf(vvp_out, ";\n");
|
|
}
|
|
|
|
for (idx = 0 ; idx < npos ; idx += 4, ecnt += 1) {
|
|
unsigned sub, top;
|
|
|
|
fprintf(vvp_out, "E_%p/%u .event posedge", obj, ecnt);
|
|
|
|
top = idx + 4;
|
|
if (npos < top)
|
|
top = npos;
|
|
for (sub = idx ; sub < top ; sub += 1) {
|
|
ivl_nexus_t nex = ivl_event_pos(obj, sub);
|
|
fprintf(vvp_out, ", %s", draw_input_from_net(nex));
|
|
}
|
|
fprintf(vvp_out, ";\n");
|
|
}
|
|
|
|
assert(ecnt == cnt);
|
|
|
|
fprintf(vvp_out, "E_%p .event/or", obj);
|
|
fprintf(vvp_out, " E_%p/0", obj);
|
|
|
|
for (idx = 1 ; idx < cnt ; idx += 1)
|
|
fprintf(vvp_out, ", E_%p/%u", obj, idx);
|
|
|
|
fprintf(vvp_out, ";\n");
|
|
|
|
} else {
|
|
unsigned num_input_strings = nany + nneg + npos;
|
|
unsigned idx;
|
|
ivl_nexus_t input_nexa[4];
|
|
const char*edge = 0;
|
|
|
|
assert(num_input_strings <= 4);
|
|
|
|
if (nany > 0) {
|
|
assert((nneg + npos) == 0);
|
|
assert(nany <= 4);
|
|
|
|
edge = "edge";
|
|
|
|
for (idx = 0 ; idx < nany ; idx += 1) {
|
|
ivl_nexus_t nex = ivl_event_any(obj, idx);
|
|
input_nexa[idx] = nex;
|
|
}
|
|
|
|
} else if (nneg > 0) {
|
|
assert((nany + npos) == 0);
|
|
edge = "negedge";
|
|
|
|
for (idx = 0 ; idx < nneg ; idx += 1) {
|
|
ivl_nexus_t nex = ivl_event_neg(obj, idx);
|
|
input_nexa[idx] = nex;
|
|
}
|
|
|
|
} else {
|
|
assert((nany + nneg) == 0);
|
|
edge = "posedge";
|
|
|
|
for (idx = 0 ; idx < npos ; idx += 1) {
|
|
ivl_nexus_t nex = ivl_event_pos(obj, idx);
|
|
input_nexa[idx] = nex;
|
|
}
|
|
}
|
|
|
|
fprintf(vvp_out, "E_%p .event %s", obj, edge);
|
|
for (idx = 0 ; idx < num_input_strings ; idx += 1)
|
|
fprintf(vvp_out, ", %s", draw_input_from_net(input_nexa[idx]));
|
|
|
|
fprintf(vvp_out, ";\n");
|
|
}
|
|
}
|
|
|
|
/*
|
|
* This function draws any functors needed to calculate the input to
|
|
* this nexus, and leaves in the data array strings that can be used
|
|
* as functor arguments. The strings are from the draw_net_input
|
|
* function, which in turn returns nexus names, so the strings are
|
|
* safe to pass around.
|
|
*/
|
|
static void draw_lpm_data_inputs(ivl_lpm_t net, unsigned base,
|
|
unsigned ndata, const char**src_table)
|
|
{
|
|
unsigned idx;
|
|
for (idx = 0 ; idx < ndata ; idx += 1) {
|
|
ivl_nexus_t nex = ivl_lpm_data(net, base+idx);
|
|
src_table[idx] = draw_net_input(nex);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* If needed, draw a .delay node to delay the output from the LPM
|
|
* device. Return the "/d" string if we drew this .delay node, or the
|
|
* "" string if the node was not needed. The caller uses that string
|
|
* to modify labels that are generated.
|
|
*/
|
|
static const char* draw_lpm_output_delay(ivl_lpm_t net)
|
|
{
|
|
ivl_expr_t d_rise = ivl_lpm_delay(net, 0);
|
|
ivl_expr_t d_fall = ivl_lpm_delay(net, 1);
|
|
ivl_expr_t d_decay = ivl_lpm_delay(net, 2);
|
|
|
|
const char*dly = "";
|
|
if (d_rise != 0) {
|
|
assert(number_is_immediate(d_rise, 64));
|
|
assert(number_is_immediate(d_fall, 64));
|
|
assert(number_is_immediate(d_decay, 64));
|
|
dly = "/d";
|
|
fprintf(vvp_out, "L_%p .delay (%lu,%lu,%lu) L_%p/d;\n",
|
|
net, get_number_immediate(d_rise),
|
|
get_number_immediate(d_rise),
|
|
get_number_immediate(d_rise), net);
|
|
}
|
|
|
|
return dly;
|
|
}
|
|
|
|
static void draw_lpm_add(ivl_lpm_t net)
|
|
{
|
|
const char*src_table[2];
|
|
unsigned width;
|
|
const char*type = "";
|
|
ivl_variable_type_t dta = data_type_of_nexus(ivl_lpm_data(net,0));
|
|
ivl_variable_type_t dtb = data_type_of_nexus(ivl_lpm_data(net,1));
|
|
ivl_variable_type_t dto = IVL_VT_LOGIC;
|
|
|
|
if (dta == IVL_VT_REAL || dtb == IVL_VT_REAL)
|
|
dto = IVL_VT_REAL;
|
|
|
|
width = ivl_lpm_width(net);
|
|
|
|
switch (ivl_lpm_type(net)) {
|
|
case IVL_LPM_ADD:
|
|
if (dto == IVL_VT_REAL)
|
|
type = "sum.r";
|
|
else
|
|
type = "sum";
|
|
break;
|
|
case IVL_LPM_SUB:
|
|
if (dto == IVL_VT_REAL)
|
|
type = "sub.r";
|
|
else
|
|
type = "sub";
|
|
break;
|
|
case IVL_LPM_MULT:
|
|
if (dto == IVL_VT_REAL)
|
|
type = "mult.r";
|
|
else
|
|
type = "mult";
|
|
break;
|
|
case IVL_LPM_DIVIDE:
|
|
if (dto == IVL_VT_REAL)
|
|
type = "div.r";
|
|
else if (ivl_lpm_signed(net))
|
|
type = "div.s";
|
|
else
|
|
type = "div";
|
|
break;
|
|
case IVL_LPM_MOD:
|
|
if (dto == IVL_VT_REAL)
|
|
type = "mod.r";
|
|
else
|
|
type = "mod";
|
|
break;
|
|
case IVL_LPM_POW:
|
|
if (dto == IVL_VT_REAL)
|
|
type = "pow.r";
|
|
else if (ivl_lpm_signed(net)) {
|
|
type = "pow.s";
|
|
if (width > 8*sizeof(long)) {
|
|
fprintf(stderr, "%s:%u: sorry (vvp-tgt): Signed power "
|
|
"result must be no more than %zu bits.\n",
|
|
ivl_lpm_file(net), ivl_lpm_lineno(net),
|
|
8*sizeof(long));
|
|
exit(1);
|
|
}
|
|
} else
|
|
type = "pow";
|
|
break;
|
|
default:
|
|
assert(0);
|
|
}
|
|
|
|
draw_lpm_data_inputs(net, 0, 2, src_table);
|
|
|
|
const char*dly = draw_lpm_output_delay(net);
|
|
|
|
fprintf(vvp_out, "L_%p%s .arith/%s %u, %s, %s;\n",
|
|
net, dly, type, width, src_table[0], src_table[1]);
|
|
}
|
|
|
|
/*
|
|
* The read port to an array is generated as a single record that takes
|
|
* the address as an input.
|
|
*/
|
|
static void draw_lpm_array(ivl_lpm_t net)
|
|
{
|
|
ivl_nexus_t nex;
|
|
ivl_signal_t mem = ivl_lpm_array(net);
|
|
|
|
fprintf(vvp_out, "L_%p .array/port v%p, ", net, mem);
|
|
|
|
nex = ivl_lpm_select(net);
|
|
fprintf(vvp_out, "%s", draw_net_input(nex));
|
|
|
|
fprintf(vvp_out, ";\n");
|
|
}
|
|
|
|
static void draw_lpm_cmp(ivl_lpm_t net)
|
|
{
|
|
const char*src_table[2];
|
|
unsigned width;
|
|
const char*type = "";
|
|
const char*signed_string = ivl_lpm_signed(net)? ".s" : "";
|
|
ivl_variable_type_t dta = data_type_of_nexus(ivl_lpm_data(net,0));
|
|
ivl_variable_type_t dtb = data_type_of_nexus(ivl_lpm_data(net,1));
|
|
ivl_variable_type_t dtc = IVL_VT_LOGIC;
|
|
|
|
if (dta == IVL_VT_REAL || dtb == IVL_VT_REAL)
|
|
dtc = IVL_VT_REAL;
|
|
|
|
width = ivl_lpm_width(net);
|
|
|
|
switch (ivl_lpm_type(net)) {
|
|
case IVL_LPM_CMP_EEQ:
|
|
assert(dtc != IVL_VT_REAL); /* Should never get here! */
|
|
type = "eeq";
|
|
signed_string = "";
|
|
break;
|
|
case IVL_LPM_CMP_EQ:
|
|
if (dtc == IVL_VT_REAL)
|
|
type = "eq.r";
|
|
else
|
|
type = "eq";
|
|
signed_string = "";
|
|
break;
|
|
case IVL_LPM_CMP_GE:
|
|
if (dtc == IVL_VT_REAL) {
|
|
type = "ge.r";
|
|
signed_string = "";
|
|
} else
|
|
type = "ge";
|
|
break;
|
|
case IVL_LPM_CMP_GT:
|
|
if (dtc == IVL_VT_REAL) {
|
|
type = "gt.r";
|
|
signed_string = "";
|
|
} else
|
|
type = "gt";
|
|
break;
|
|
case IVL_LPM_CMP_NE:
|
|
if (dtc == IVL_VT_REAL)
|
|
type = "ne.r";
|
|
else
|
|
type = "ne";
|
|
signed_string = "";
|
|
break;
|
|
case IVL_LPM_CMP_NEE:
|
|
assert(dtc != IVL_VT_REAL); /* Should never get here! */
|
|
type = "nee";
|
|
signed_string = "";
|
|
break;
|
|
default:
|
|
assert(0);
|
|
}
|
|
|
|
draw_lpm_data_inputs(net, 0, 2, src_table);
|
|
|
|
const char*dly = draw_lpm_output_delay(net);
|
|
|
|
fprintf(vvp_out, "L_%p%s .cmp/%s%s %u, %s, %s;\n",
|
|
net, dly, type, signed_string, width,
|
|
src_table[0], src_table[1]);
|
|
}
|
|
|
|
/*
|
|
* This function draws the arguments to a .const node using the
|
|
* lpm inputs starting at "start" and for "cnt" inputs. This input
|
|
* count must be <= 4. It is up to the caller to write the header part
|
|
* of the statement, and to organize the data into multiple
|
|
* statements.
|
|
*
|
|
* Return the width of the final concatenation.
|
|
*/
|
|
static unsigned lpm_concat_inputs(ivl_lpm_t net, unsigned start,
|
|
unsigned cnt, const char*src_table[])
|
|
{
|
|
unsigned idx;
|
|
unsigned wid = 0;
|
|
|
|
assert(cnt <= 4);
|
|
|
|
/* First, draw the [L M N O] part of the statement, the list
|
|
of widths for the .concat statement. */
|
|
fprintf(vvp_out, "[");
|
|
|
|
for (idx = 0 ; idx < cnt ; idx += 1) {
|
|
ivl_nexus_t nex = ivl_lpm_data(net, start+idx);
|
|
unsigned nexus_width = width_of_nexus(nex);
|
|
fprintf(vvp_out, " %u", nexus_width);
|
|
wid += nexus_width;
|
|
}
|
|
|
|
for ( ; idx < 4 ; idx += 1)
|
|
fprintf(vvp_out, " 0");
|
|
|
|
fprintf(vvp_out, "]");
|
|
|
|
|
|
for (idx = 0 ; idx < cnt ; idx += 1) {
|
|
fprintf(vvp_out, ", %s", src_table[idx]);
|
|
}
|
|
|
|
fprintf(vvp_out, ";\n");
|
|
return wid;
|
|
}
|
|
|
|
/*
|
|
* Implement the general IVL_LPM_CONCAT using .concat nodes. Use as
|
|
* many nested nodes as necessary to support the desired number of
|
|
* input vectors.
|
|
*/
|
|
static void draw_lpm_concat(ivl_lpm_t net)
|
|
{
|
|
const char*src_table[4];
|
|
unsigned icnt = ivl_lpm_selects(net);
|
|
const char*dly = draw_lpm_output_delay(net);
|
|
|
|
if (icnt <= 4) {
|
|
/* This is the easiest case. There are 4 or fewer input
|
|
vectors, so the entire IVL_LPM_CONCAT can be
|
|
implemented with a single .concat node. */
|
|
draw_lpm_data_inputs(net, 0, icnt, src_table);
|
|
fprintf(vvp_out, "L_%p%s .concat ", net, dly);
|
|
lpm_concat_inputs(net, 0, icnt, src_table);
|
|
|
|
} else {
|
|
/* If there are more than 4 inputs, things get more
|
|
complicated. We need to generate a balanced tree of
|
|
.concat nodes to blend the inputs down to a single
|
|
root node, that becomes the output from the
|
|
concatenation. */
|
|
unsigned idx, depth;
|
|
struct concat_tree {
|
|
unsigned base;
|
|
unsigned wid;
|
|
} *tree;
|
|
|
|
tree = malloc((icnt + 3)/4 * sizeof(struct concat_tree));
|
|
|
|
/* First, fill in all the leaves with the initial inputs
|
|
to the tree. After this loop, there are (icnt+3)/4
|
|
.concat nodes drawn, that together take all the
|
|
inputs. */
|
|
for (idx = 0 ; idx < icnt ; idx += 4) {
|
|
unsigned wid = 0;
|
|
unsigned trans = 4;
|
|
if ((idx + trans) > icnt)
|
|
trans = icnt - idx;
|
|
|
|
draw_lpm_data_inputs(net, idx, trans, src_table);
|
|
fprintf(vvp_out, "LS_%p_0_%u .concat ", net, idx);
|
|
wid = lpm_concat_inputs(net, idx, trans, src_table);
|
|
|
|
tree[idx/4].base = idx;
|
|
tree[idx/4].wid = wid;
|
|
}
|
|
|
|
/* icnt is the input count for the level. It is the
|
|
number of .concats of the previous level that have to
|
|
be concatenated at the current level. (This is not
|
|
the same as the bit width.) */
|
|
icnt = (icnt + 3)/4;
|
|
|
|
/* Tree now has icnt nodes that are depth=0 concat nodes
|
|
which take in the leaf inputs. The while loop below
|
|
starts and ends with a tree of icnt nodes. Each time
|
|
through, there are 1/4 the nodes we started
|
|
with. Thus, we eventually get down to <=4 nodes, and
|
|
that is when we fall out of the loop. */
|
|
|
|
depth = 1;
|
|
while (icnt > 4) {
|
|
for (idx = 0 ; idx < icnt ; idx += 4) {
|
|
unsigned tdx;
|
|
unsigned wid = 0;
|
|
unsigned trans = 4;
|
|
if ((idx+trans) > icnt)
|
|
trans = icnt - idx;
|
|
|
|
fprintf(vvp_out, "LS_%p_%u_%u .concat [",
|
|
net, depth, idx);
|
|
|
|
for (tdx = 0 ; tdx < trans ; tdx += 1) {
|
|
fprintf(vvp_out, " %u", tree[idx+tdx].wid);
|
|
wid += tree[idx+tdx].wid;
|
|
}
|
|
|
|
for ( ; tdx < 4 ; tdx += 1)
|
|
fprintf(vvp_out, " 0");
|
|
|
|
fprintf(vvp_out, "]");
|
|
|
|
for (tdx = 0; tdx < trans ; tdx += 1) {
|
|
fprintf(vvp_out, ", LS_%p_%u_%u", net,
|
|
depth-1, tree[idx+tdx].base);
|
|
}
|
|
|
|
fprintf(vvp_out, ";\n");
|
|
tree[idx/4].base = idx;
|
|
tree[idx/4].wid = wid;
|
|
}
|
|
|
|
depth += 1;
|
|
icnt = (icnt + 3)/4;
|
|
}
|
|
|
|
/* Finally, draw the root node that takes in the final
|
|
row of tree nodes and generates a single output. */
|
|
fprintf(vvp_out, "L_%p%s .concat [", net, dly);
|
|
for (idx = 0 ; idx < icnt ; idx += 1)
|
|
fprintf(vvp_out, " %u", tree[idx].wid);
|
|
for ( ; idx < 4 ; idx += 1)
|
|
fprintf(vvp_out, " 0");
|
|
fprintf(vvp_out, "]");
|
|
|
|
for (idx = 0 ; idx < icnt ; idx += 1)
|
|
fprintf(vvp_out, ", LS_%p_%u_%u",
|
|
net, depth-1, tree[idx].base);
|
|
|
|
fprintf(vvp_out, ";\n");
|
|
free(tree);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* primitive FD (q, clk, ce, d);
|
|
* output q;
|
|
* reg q;
|
|
* input clk, ce, d;
|
|
* table
|
|
* // clk ce d r s q q+
|
|
* r 1 0 0 0 : ? : 0;
|
|
* r 1 1 0 0 : ? : 1;
|
|
* f 1 ? 0 0 : ? : -;
|
|
* ? 1 ? 0 0 : ? : -;
|
|
* * 0 ? 0 0 : ? : -;
|
|
* ? ? ? 1 ? : ? : 0;
|
|
* ? ? ? 0 1 : ? : 1;
|
|
* endtable
|
|
* endprimitive
|
|
*/
|
|
static void draw_lpm_ff(ivl_lpm_t net)
|
|
{
|
|
ivl_expr_t aset_expr = 0;
|
|
const char*aset_bits = 0;
|
|
|
|
ivl_nexus_t nex;
|
|
unsigned width;
|
|
|
|
width = ivl_lpm_width(net);
|
|
|
|
aset_expr = ivl_lpm_aset_value(net);
|
|
if (aset_expr) {
|
|
assert(ivl_expr_width(aset_expr) == width);
|
|
aset_bits = ivl_expr_bits(aset_expr);
|
|
}
|
|
|
|
|
|
fprintf(vvp_out, "L_%p .dff ", net);
|
|
|
|
nex = ivl_lpm_data(net,0);
|
|
assert(nex);
|
|
fprintf(vvp_out, "%s", draw_net_input(nex));
|
|
|
|
nex = ivl_lpm_clk(net);
|
|
assert(nex);
|
|
fprintf(vvp_out, ", %s", draw_net_input(nex));
|
|
|
|
nex = ivl_lpm_enable(net);
|
|
if (nex) {
|
|
fprintf(vvp_out, ", %s", draw_net_input(nex));
|
|
} else {
|
|
fprintf(vvp_out, ", C4<1>");
|
|
}
|
|
|
|
/* Stub asynchronous input for now. */
|
|
fprintf(vvp_out, ", C4<z>");
|
|
|
|
fprintf(vvp_out, ";\n");
|
|
}
|
|
|
|
static void draw_lpm_shiftl(ivl_lpm_t net)
|
|
{
|
|
unsigned width = ivl_lpm_width(net);
|
|
const char* signed_flag = ivl_lpm_signed(net)? "s" : "";
|
|
const char*dly = draw_lpm_output_delay(net);
|
|
|
|
if (ivl_lpm_type(net) == IVL_LPM_SHIFTR)
|
|
fprintf(vvp_out, "L_%p%s .shift/r%s %u", net, dly, signed_flag,
|
|
width);
|
|
else
|
|
fprintf(vvp_out, "L_%p%s .shift/l %u", net, dly, width);
|
|
|
|
fprintf(vvp_out, ", %s", draw_net_input(ivl_lpm_data(net, 0)));
|
|
|
|
fprintf(vvp_out, ", %s", draw_net_input(ivl_lpm_data(net, 1)));
|
|
|
|
fprintf(vvp_out, ";\n");
|
|
}
|
|
|
|
static void draw_type_string_of_nex(ivl_nexus_t nex)
|
|
{
|
|
switch (data_type_of_nexus(nex)) {
|
|
case IVL_VT_REAL:
|
|
fprintf(vvp_out, "r");
|
|
break;
|
|
case IVL_VT_LOGIC:
|
|
case IVL_VT_BOOL:
|
|
fprintf(vvp_out, "v%d", width_of_nexus(nex));
|
|
break;
|
|
default:
|
|
assert(0);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void draw_lpm_sfunc(ivl_lpm_t net)
|
|
{
|
|
unsigned idx;
|
|
|
|
const char*dly = draw_lpm_output_delay(net);
|
|
|
|
fprintf(vvp_out, "L_%p%s .sfunc %u %u \"%s\"", net, dly,
|
|
ivl_file_table_index(ivl_lpm_file(net)), ivl_lpm_lineno(net),
|
|
ivl_lpm_string(net));
|
|
|
|
/* Print the function type descriptor string. */
|
|
fprintf(vvp_out, ", \"");
|
|
|
|
draw_type_string_of_nex(ivl_lpm_q(net,0));
|
|
|
|
for (idx = 0 ; idx < ivl_lpm_size(net) ; idx += 1)
|
|
draw_type_string_of_nex(ivl_lpm_data(net,idx));
|
|
|
|
fprintf(vvp_out, "\"");
|
|
|
|
for (idx = 0 ; idx < ivl_lpm_size(net) ; idx += 1) {
|
|
fprintf(vvp_out, ", %s", draw_net_input(ivl_lpm_data(net,idx)));
|
|
}
|
|
|
|
fprintf(vvp_out, ";\n");
|
|
}
|
|
|
|
static void draw_lpm_ufunc(ivl_lpm_t net)
|
|
{
|
|
unsigned idx;
|
|
ivl_scope_t def = ivl_lpm_define(net);
|
|
|
|
const char*dly = draw_lpm_output_delay(net);
|
|
|
|
fprintf(vvp_out, "L_%p%s .ufunc TD_%s, %u", net, dly,
|
|
vvp_mangle_id(ivl_scope_name(def)),
|
|
ivl_lpm_width(net));
|
|
|
|
/* Print all the net signals that connect to the input of the
|
|
function. */
|
|
for (idx = 0 ; idx < ivl_lpm_size(net) ; idx += 1) {
|
|
fprintf(vvp_out, ", %s", draw_net_input(ivl_lpm_data(net, idx)));
|
|
}
|
|
|
|
|
|
assert((ivl_lpm_size(net)+1) == ivl_scope_ports(def));
|
|
|
|
/* Now print all the variables in the function scope that
|
|
receive the input values given in the previous list. */
|
|
for (idx = 0 ; idx < ivl_lpm_size(net) ; idx += 1) {
|
|
ivl_signal_t psig = ivl_scope_port(def, idx+1);
|
|
|
|
if (idx == 0)
|
|
fprintf(vvp_out, " (");
|
|
else
|
|
fprintf(vvp_out, ", ");
|
|
|
|
assert(ivl_signal_array_count(psig) == 1);
|
|
fprintf(vvp_out, "v%p_0", psig);
|
|
}
|
|
|
|
fprintf(vvp_out, ")");
|
|
|
|
/* Finally, print the reference to the signal from which the
|
|
result is collected. */
|
|
{ ivl_signal_t psig = ivl_scope_port(def, 0);
|
|
assert(ivl_lpm_width(net) == ivl_signal_width(psig));
|
|
assert(ivl_signal_array_count(psig) == 1);
|
|
|
|
fprintf(vvp_out, " v%p_0", psig);
|
|
}
|
|
|
|
fprintf(vvp_out, ";\n");
|
|
}
|
|
|
|
/*
|
|
* Handle a PART SELECT device. This has a single input and output,
|
|
* plus an optional extra input that is a non-constant base.
|
|
*/
|
|
static void draw_lpm_part(ivl_lpm_t net)
|
|
{
|
|
unsigned width, base;
|
|
ivl_nexus_t sel;
|
|
|
|
const char*dly = draw_lpm_output_delay(net);
|
|
|
|
width = ivl_lpm_width(net);
|
|
base = ivl_lpm_base(net);
|
|
sel = ivl_lpm_data(net,1);
|
|
|
|
if (sel == 0) {
|
|
fprintf(vvp_out, "L_%p%s .part %s",
|
|
net, dly, draw_net_input(ivl_lpm_data(net, 0)));
|
|
fprintf(vvp_out, ", %u, %u;\n", base, width);
|
|
} else {
|
|
fprintf(vvp_out, "L_%p%s .part/v %s",
|
|
net, dly, draw_net_input(ivl_lpm_data(net,0)));
|
|
fprintf(vvp_out, ", %s", draw_net_input(sel));
|
|
fprintf(vvp_out, ", %u;\n", width);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Handle a PART SELECT PV device. Generate a .part/pv node that
|
|
* includes the part input, and the geometry of the part.
|
|
*/
|
|
static void draw_lpm_part_pv(ivl_lpm_t net)
|
|
{
|
|
unsigned width = ivl_lpm_width(net);
|
|
unsigned base = ivl_lpm_base(net);
|
|
unsigned signal_width = width_of_nexus(ivl_lpm_q(net,0));
|
|
|
|
fprintf(vvp_out, "L_%p .part/pv %s",
|
|
net, draw_net_input(ivl_lpm_data(net, 0)));
|
|
|
|
fprintf(vvp_out, ", %u, %u, %u;\n", base, width, signal_width);
|
|
}
|
|
|
|
/*
|
|
* Handle the drawing of a bi-directional part select. The two ports
|
|
* are simultaneously input and output. A simple minded connect of the
|
|
* input to the output causes a functor cycle which will lock into an
|
|
* X value, so something special is needed.
|
|
*
|
|
* NOTE: The inputs of the tran device at this point need to be from
|
|
* all the drivers of the nexus *except* the tran itself. This
|
|
* function will draw three labels that can be linked:
|
|
*
|
|
* The ivl_lpm_q of a part(bi) may be a smaller vector then the
|
|
* ivl_lpm_data, the tran acts like a forward part select in that
|
|
* way.
|
|
*
|
|
* The device creates these nodes:
|
|
*
|
|
* - L_%p/i
|
|
* This is the Q port of the tran resolved and padded to the maximum
|
|
* width of the tran. The tran itself is not included in the
|
|
* resolution of this port.
|
|
*
|
|
* - L_%p/V
|
|
* This is the Q and D parts resolved together, still without the tran
|
|
* driving anything.
|
|
*
|
|
* - L_%p/P
|
|
* This is the /V node part-selected back to the dimensions of the Q
|
|
* side.
|
|
*/
|
|
static void draw_lpm_part_bi(ivl_lpm_t net)
|
|
{
|
|
unsigned width = ivl_lpm_width(net);
|
|
unsigned base = ivl_lpm_base(net);
|
|
unsigned signal_width = width_of_nexus(ivl_lpm_data(net,0));
|
|
|
|
unsigned idx;
|
|
ivl_nexus_t nex;
|
|
ivl_nexus_ptr_t ptr = 0;
|
|
|
|
char*p_str;
|
|
char*v_str;
|
|
|
|
/* It seems implausible that the two inputs of a tran will be
|
|
connected together. So assert that this is so to simplify
|
|
the code to look for the nexus_ptr_t objects. */
|
|
assert(ivl_lpm_q(net,0) != ivl_lpm_data(net,0));
|
|
|
|
nex = ivl_lpm_q(net,0);
|
|
for (idx = 0 ; idx < ivl_nexus_ptrs(nex) ; idx += 1) {
|
|
ptr = ivl_nexus_ptr(nex, idx);
|
|
if (ivl_nexus_ptr_lpm(ptr) == net)
|
|
break;
|
|
}
|
|
assert(ptr != 0);
|
|
p_str = draw_net_input_x(nex, ptr, 0, 0);
|
|
|
|
nex = ivl_lpm_data(net,0);
|
|
for (idx = 0 ; idx < ivl_nexus_ptrs(nex) ; idx += 1) {
|
|
ptr = ivl_nexus_ptr(nex, idx);
|
|
if (ivl_nexus_ptr_lpm(ptr) == net)
|
|
break;
|
|
}
|
|
v_str = draw_net_input_x(nex, ptr, OMIT_PART_BI_DATA, 0);
|
|
|
|
/* Pad the part-sized input out to a common width...
|
|
The /i label is the Q side of the tran, resolved except for
|
|
the tran itself and padded (with z) to the larger width. */
|
|
fprintf(vvp_out, "L_%p/i .part/pv %s, %u, %u, %u;\n",
|
|
net, p_str, base, width, signal_width);
|
|
|
|
/* Resolve together the two halves of the tran...
|
|
The /V label is the ports of the tran (now the same width)
|
|
resolved together. Neither input to this resolver includes
|
|
the tran itself. */
|
|
fprintf(vvp_out, "L_%p/V .resolv tri, L_%p/i, %s;\n",
|
|
net, net, v_str);
|
|
|
|
/* The full-width side is created by the tran device, all we
|
|
have left to to is take a part select of that for the
|
|
smaller output, and this becomes the part select output of
|
|
the BI device. */
|
|
fprintf(vvp_out, "L_%p/P .part L_%p/V, %u, %u;\n", net,
|
|
net, base, width);
|
|
|
|
free(p_str);
|
|
free(v_str);
|
|
}
|
|
|
|
/*
|
|
* Draw unary reduction devices.
|
|
*/
|
|
static void draw_lpm_re(ivl_lpm_t net, const char*type)
|
|
{
|
|
const char*dly = draw_lpm_output_delay(net);
|
|
|
|
fprintf(vvp_out, "L_%p%s .reduce/%s %s;\n", net, dly,
|
|
type, draw_net_input(ivl_lpm_data(net,0)));
|
|
}
|
|
|
|
static void draw_lpm_repeat(ivl_lpm_t net)
|
|
{
|
|
fprintf(vvp_out, "L_%p .repeat %u, %u, %s;\n", net,
|
|
ivl_lpm_width(net), ivl_lpm_size(net),
|
|
draw_net_input(ivl_lpm_data(net,0)));
|
|
}
|
|
|
|
static void draw_lpm_sign_ext(ivl_lpm_t net)
|
|
{
|
|
fprintf(vvp_out, "L_%p .extend/s %u, %s;\n",
|
|
net, ivl_lpm_width(net),
|
|
draw_net_input(ivl_lpm_data(net,0)));
|
|
}
|
|
|
|
static void draw_lpm_in_scope(ivl_lpm_t net)
|
|
{
|
|
switch (ivl_lpm_type(net)) {
|
|
|
|
case IVL_LPM_ADD:
|
|
case IVL_LPM_SUB:
|
|
case IVL_LPM_MULT:
|
|
case IVL_LPM_DIVIDE:
|
|
case IVL_LPM_MOD:
|
|
case IVL_LPM_POW:
|
|
draw_lpm_add(net);
|
|
return;
|
|
|
|
case IVL_LPM_ARRAY:
|
|
draw_lpm_array(net);
|
|
return;
|
|
|
|
case IVL_LPM_PART_BI:
|
|
draw_lpm_part_bi(net);
|
|
return;
|
|
|
|
case IVL_LPM_PART_VP:
|
|
draw_lpm_part(net);
|
|
return;
|
|
|
|
case IVL_LPM_PART_PV:
|
|
draw_lpm_part_pv(net);
|
|
return;
|
|
|
|
case IVL_LPM_CONCAT:
|
|
draw_lpm_concat(net);
|
|
return;
|
|
|
|
case IVL_LPM_FF:
|
|
draw_lpm_ff(net);
|
|
return;
|
|
|
|
case IVL_LPM_CMP_EEQ:
|
|
case IVL_LPM_CMP_EQ:
|
|
case IVL_LPM_CMP_GE:
|
|
case IVL_LPM_CMP_GT:
|
|
case IVL_LPM_CMP_NE:
|
|
case IVL_LPM_CMP_NEE:
|
|
draw_lpm_cmp(net);
|
|
return;
|
|
|
|
case IVL_LPM_MUX:
|
|
draw_lpm_mux(net);
|
|
return;
|
|
|
|
case IVL_LPM_RE_AND:
|
|
draw_lpm_re(net, "and");
|
|
return;
|
|
case IVL_LPM_RE_OR:
|
|
draw_lpm_re(net, "or");
|
|
return;
|
|
case IVL_LPM_RE_XOR:
|
|
draw_lpm_re(net, "xor");
|
|
return;
|
|
case IVL_LPM_RE_NAND:
|
|
draw_lpm_re(net, "nand");
|
|
return;
|
|
case IVL_LPM_RE_NOR:
|
|
draw_lpm_re(net, "nor");
|
|
return;
|
|
case IVL_LPM_RE_XNOR:
|
|
draw_lpm_re(net, "xnor");
|
|
return;
|
|
|
|
case IVL_LPM_REPEAT:
|
|
draw_lpm_repeat(net);
|
|
return;
|
|
|
|
case IVL_LPM_SHIFTL:
|
|
case IVL_LPM_SHIFTR:
|
|
draw_lpm_shiftl(net);
|
|
return;
|
|
|
|
case IVL_LPM_SIGN_EXT:
|
|
draw_lpm_sign_ext(net);
|
|
return;
|
|
|
|
case IVL_LPM_SFUNC:
|
|
draw_lpm_sfunc(net);
|
|
return;
|
|
|
|
case IVL_LPM_UFUNC:
|
|
draw_lpm_ufunc(net);
|
|
return;
|
|
|
|
default:
|
|
fprintf(stderr, "XXXX LPM not supported: %s.%s\n",
|
|
ivl_scope_name(ivl_lpm_scope(net)), ivl_lpm_basename(net));
|
|
}
|
|
}
|
|
|
|
int draw_scope(ivl_scope_t net, ivl_scope_t parent)
|
|
{
|
|
unsigned idx;
|
|
const char *type;
|
|
switch (ivl_scope_type(net)) {
|
|
case IVL_SCT_MODULE: type = "module"; break;
|
|
case IVL_SCT_FUNCTION: type = "function"; break;
|
|
case IVL_SCT_TASK: type = "task"; break;
|
|
case IVL_SCT_BEGIN: type = "begin"; break;
|
|
case IVL_SCT_FORK: type = "fork"; break;
|
|
case IVL_SCT_GENERATE: type = "generate"; break;
|
|
default: type = "?"; assert(0);
|
|
}
|
|
|
|
fprintf(vvp_out, "S_%p .scope %s, \"%s\" \"%s\" %d %d",
|
|
net, type, vvp_mangle_name(ivl_scope_basename(net)),
|
|
ivl_scope_tname(net), ivl_file_table_index(ivl_scope_file(net)),
|
|
ivl_scope_lineno(net));
|
|
|
|
if (parent) {
|
|
fprintf(vvp_out, ", %d %d, S_%p;\n",
|
|
ivl_file_table_index(ivl_scope_def_file(net)),
|
|
ivl_scope_def_lineno(net), parent);
|
|
} else {
|
|
|
|
fprintf(vvp_out, ";\n");
|
|
}
|
|
|
|
fprintf(vvp_out, " .timescale %d %d;\n", ivl_scope_time_units(net),
|
|
ivl_scope_time_precision(net));
|
|
|
|
for (idx = 0 ; idx < ivl_scope_params(net) ; idx += 1) {
|
|
ivl_parameter_t par = ivl_scope_param(net, idx);
|
|
ivl_expr_t pex = ivl_parameter_expr(par);
|
|
switch (ivl_expr_type(pex)) {
|
|
case IVL_EX_STRING:
|
|
fprintf(vvp_out, "P_%p .param/str \"%s\" %d %d, \"%s\";\n",
|
|
par, ivl_parameter_basename(par),
|
|
ivl_file_table_index(ivl_parameter_file(par)),
|
|
ivl_parameter_lineno(par),
|
|
ivl_expr_string(pex));
|
|
break;
|
|
case IVL_EX_NUMBER:
|
|
fprintf(vvp_out, "P_%p .param/l \"%s\" %d %d, %sC4<",
|
|
par, ivl_parameter_basename(par),
|
|
ivl_file_table_index(ivl_parameter_file(par)),
|
|
ivl_parameter_lineno(par),
|
|
ivl_expr_signed(pex)? "+":"");
|
|
{ const char*bits = ivl_expr_bits(pex);
|
|
unsigned nbits = ivl_expr_width(pex);
|
|
unsigned bb;
|
|
for (bb = 0 ; bb < nbits; bb += 1)
|
|
fprintf(vvp_out, "%c", bits[nbits-bb-1]);
|
|
}
|
|
fprintf(vvp_out, ">;\n");
|
|
break;
|
|
case IVL_EX_REALNUM:
|
|
fprintf(vvp_out, "P_%p .param/real \"%s\" %d %d, %s; value=%g\n",
|
|
par, ivl_parameter_basename(par),
|
|
ivl_file_table_index(ivl_parameter_file(par)),
|
|
ivl_parameter_lineno(par),
|
|
draw_Cr_to_string(ivl_expr_dvalue(pex)),
|
|
ivl_expr_dvalue(pex));
|
|
break;
|
|
default:
|
|
fprintf(vvp_out, "; parameter type %d unsupported\n",
|
|
ivl_expr_type(pex));
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* Scan the scope for logic devices. For each device, draw out
|
|
a functor that connects pin 0 to the output, and the
|
|
remaining pins to inputs. */
|
|
|
|
for (idx = 0 ; idx < ivl_scope_logs(net) ; idx += 1) {
|
|
ivl_net_logic_t lptr = ivl_scope_log(net, idx);
|
|
draw_logic_in_scope(lptr);
|
|
}
|
|
|
|
|
|
/* Scan the signals (reg and net) and draw the appropriate
|
|
statements to make the signal function. */
|
|
|
|
for (idx = 0 ; idx < ivl_scope_sigs(net) ; idx += 1) {
|
|
ivl_signal_t sig = ivl_scope_sig(net, idx);
|
|
|
|
switch (ivl_signal_type(sig)) {
|
|
case IVL_SIT_REG:
|
|
draw_reg_in_scope(sig);
|
|
break;
|
|
default:
|
|
draw_net_in_scope(sig);
|
|
break;
|
|
}
|
|
}
|
|
|
|
for (idx = 0 ; idx < ivl_scope_events(net) ; idx += 1) {
|
|
ivl_event_t event = ivl_scope_event(net, idx);
|
|
draw_event_in_scope(event);
|
|
}
|
|
|
|
for (idx = 0 ; idx < ivl_scope_lpms(net) ; idx += 1) {
|
|
ivl_lpm_t lpm = ivl_scope_lpm(net, idx);
|
|
draw_lpm_in_scope(lpm);
|
|
}
|
|
|
|
if (ivl_scope_type(net) == IVL_SCT_TASK)
|
|
draw_task_definition(net);
|
|
|
|
if (ivl_scope_type(net) == IVL_SCT_FUNCTION)
|
|
draw_func_definition(net);
|
|
|
|
ivl_scope_children(net, (ivl_scope_f*) draw_scope, net);
|
|
return 0;
|
|
}
|