Types of bits

M

MikeWhy

I'm tired, frustrated, and frankly, maybe don't entirely know what I'm
doing. But what exactly does the type system do for you aside from beat you
over the head constantly?

Set that aside for the moment...

I wrote a recursive function to flatten an array of arrays of bits. It
synthesizes fine. It should; it's only a bunch of wires. Simulation in ISIM,
though, is heavily skewed and offset, apparently trying to simulate some
perceived logic delays.


subtype byte_t is std_logic_vector(7 downto 0);
type byte_array_t is array (natural range <>) of byte_t;

function flatten (foo : byte_array_t) return std_logic_vector;

----------------------------
function flatten (foo : byte_array_t) return std_logic_vector is
begin
if (foo'length = 1) then
return foo(foo'low);
elsif (foo'ascending) then
return foo(foo'low) & flatten(foo(foo'low+1 to foo'high));
else
return foo(foo'high) & flatten(foo(foo'high-1 downto foo'low));
end if;
end function;
----------------------------

I have a matched set for 7-bit and 6-bit words, too. I think I can cut &
paste a few more variants without imploding. It was the simulation that was
the last straw.

It's a bunch of bits. Everything's a bunch of bits. Why oh why can't we have
tools that understand bits?

What am I doing wrong?
 
T

Tricky

I'm tired, frustrated, and frankly, maybe don't entirely know what I'm
doing. But what exactly does the type system do for you aside from beat you
over the head constantly?

Set that aside for the moment...

I wrote a recursive function to flatten an array of arrays of bits. It
synthesizes fine. It should; it's only a bunch of wires. Simulation in ISIM,
though, is heavily skewed and offset, apparently trying to simulate some
perceived logic delays.


subtype byte_t is std_logic_vector(7 downto 0);
type byte_array_t is array (natural range <>) of byte_t;

function flatten (foo : byte_array_t) return std_logic_vector;

----------------------------
function flatten (foo : byte_array_t) return std_logic_vector is
begin
if (foo'length = 1) then
return foo(foo'low);
elsif (foo'ascending) then
return foo(foo'low) & flatten(foo(foo'low+1 to foo'high));
else
return foo(foo'high) & flatten(foo(foo'high-1 downto foo'low));
end if;
end function;
----------------------------

I have a matched set for 7-bit and 6-bit words, too. I think I can cut &
paste a few more variants without imploding. It was the simulation that was
the last straw.

It's a bunch of bits. Everything's a bunch of bits. Why oh why can't we have
tools that understand bits?

What am I doing wrong?

I think the questions is why do you need to flatten it? whats wrong with just having ports of byte_array_t? Thats why we have the type system (so they you use the types as much as possible).

What exactly are the problems in ISIM? There should be no logic (delta) delays, because you're just assigning one large array.

or why not simply use a for loop, rather than recursive functions?
 
M

MikeWhy

Tricky said:
I think the questions is why do you need to flatten it? whats wrong with
just having ports of byte_array_t? Thats why we have the type system (so
they you use the types as much as possible).

A fair enough question. It's being written into a 64-bit wide FIFO. They
came in as 7-bit words, hence the initial lumped treatment.
What exactly are the problems in ISIM? There should be no logic (delta)
delays, because you're just assigning one large array.

The ISIM thing was self-inflicted. A clocked process got hold of the wrong
clock. Things are happily aligned on clock edges again. ;)

That's the last time I'll name signals for instantiation ports. I'm sure
rd_clk and wr_clk were meaningful enough in their original context. They
lacked the semantic descriptiveness to grab my attention here.
or why not simply use a for loop, rather than recursive functions?

As in:

for i in foo'range loop
-- descending
tmp(dumb_goofy_calc(i) downto other_goofy_calc(i)) := foo(i);
end loop;
return tmp;

Recursing on concatenation saved me a burst blood vessel, from having to
write yet another pair of dumb_goofy_calc(i) functions. I fear I'll bleed
from the eyes if I try to write just one more.

I'd love to hear a better way, preferably without constant reinvention or
even copy/paste. Recursion is the closest I've come. Just change the formal
arg type.

===========
All that aside, I write software by day. The above type of wire jiggling is
almost akin to C++ template meta programming. It's a compile time thing, not
run-time. C++ has variadic templates, which can do some big magic in the
right hands. Even simple template parameters for the actual type will make
this a non-issue. Just write it once, and let it synthesize with the actual
type passed. That's the key. I find myself continually rewriting noisy,
tedious things like the above, rather than solving the problem at hand. A
good language supports its intended use. I don't see that in the type
system's stubborness.

I'm thinking the type system saved me once or twice from trying to shove,
say, 8 bits into some other shaped signal. Meanwhile, I've about wore out
this keyboard with noisy constructs like
std_logic_vector(to_unsigned(.....)). I joke that my copy and paste keys
will break off one day, but the truth is the developing repetitive stress
injury in those fingers will preclude that.

I've been building software with various languages for 24 years. I can think
of only one language so mean spiritedly aggressive about confounding your
wishes.

And that was the other, implicit question. A mature perspective will
obviously have come to terms with the little niggles above. What's the
magic, the secret to its understanding? Surely it can't be the type system
that is VHDL's strength.
 
T

Tricky

And that was the other, implicit question. A mature perspective will
obviously have come to terms with the little niggles above. What's the
magic, the secret to its understanding? Surely it can't be the type system
that is VHDL's strength.

IMHO, if you're constantly having to do type conversions, you're doing it wrong. It is intended that you keep signals in the same type for as long as possible. My question is if you have an integer, why are you even making ita std_logic_vector? The only reason I ever convert integers is when I input them into some IP block that only has slv ports, and I dont use them veryoften. Now compilers are very good at handling all types, and even at the top level, as long as you have a bit type, it doesnt complain about mappingthe ports.
 
A

Andy

First, about VHDL typing. The first design where you have to do
unsigned arithmetic in some places, signed in others, and fixed point
or even floating point (of both signed and unsigned representations)
in yet others, you'll thank your lucky stars that VHDL has strong
types. Ditto for big endian and little endian byte vectors, with
overloaded conversions to unsigned/SLV. No strong types, no
overloading. Records, arrays, records of arrays, arrays of records:
all these things make keeping a complex design in focus so much
easier, and assist & enforce the coding practices that help make your
design understandable, reviewable and maintainable.

Hints that help me a lot:

Once inside the top level (external pin interface) I use unsigned
almost everywhere instead of std_logic_vector ("unsigned" is less
typing, and it does more).

I usually define a "subtype slv is std_logic_vector;" as an
abbreviation to reduce typing as well ("slv(to_unsigned())"). Note,
there is a new package in numeric_std for vhdl-2008 that defines
arithmetic operators that assume an unsigned interpretation of SLV.

If it works within integer's ~32 bit (signed) range limits in most
tools, I use subtypes of integer/natural instead.

Rather than have conditionals handle different range direction/start/
end on unconstrained arguments, I normalize the argument's range on a
variable up front, then use the variable instead of the argument:
variable narg : unsigned(arg'length - 1 downto 0) := arg; --
normalized arg


Andy
 
R

Rob Gaddi

And that was the other, implicit question. A mature perspective will
obviously have come to terms with the little niggles above. What's the
magic, the secret to its understanding? Surely it can't be the type system
that is VHDL's strength.

VHDL's type system has very strong opinions about how it intends to be
used. Once you've wrapped your head around it, it actively and
aggressively keeps the barrel of the gun away from your foot.

My general rule is that a std_logic_vector only gets used to represent
things that are fundamentally not of a known numeric type (bitmasks,
Johnson counters, bus data that will have different interpretations
based on which register it's addressing, etc.) Otherwise it's an
integer if it can be, a unsigned/signed if it has to be.

Likewise, anywhere booleans can be used instead of std_logic makes the
world that much better of a place.

The only other place to be using sl/slv is when you have to shim in or
out of someone else's IP. It's to my mind the biggest reason to avoid
using CoreGens, Megafunctions, and their ilk.

Other folks have asked you why you aren't just keeping your data in the
byte array form, but I'd ask the other question: Why try to keep your
data as a byte array rather than a wide data element like a
std_logic_vector(63 downto 0)? If you need it to have byte division
boundaries at different places at different times, then only break it
up where and as you need to. If you're thinking of it as "just a
collection of wires", then make it just a collection of wires.
 
M

MikeWhy

Rob Gaddi said:
VHDL's type system has very strong opinions about how it intends to be
used. Once you've wrapped your head around it, it actively and
aggressively keeps the barrel of the gun away from your foot.

My general rule is that a std_logic_vector only gets used to represent
things that are fundamentally not of a known numeric type (bitmasks,
Johnson counters, bus data that will have different interpretations
based on which register it's addressing, etc.) Otherwise it's an
integer if it can be, a unsigned/signed if it has to be.

Likewise, anywhere booleans can be used instead of std_logic makes the
world that much better of a place.

The only other place to be using sl/slv is when you have to shim in or
out of someone else's IP. It's to my mind the biggest reason to avoid
using CoreGens, Megafunctions, and their ilk.

I appreciate the thoughtful responses. All have been very helpful.

I get it about polarized and segmented "plugs" in physical wiring. That
there's a ribbon cable in between makes them all the more valuable, not
less. I totally get that.

Part of my problem was an innate sense that this "doesn't belong". It wasn't
entirely a language issue. It was a consequence of where I partitioned the
modules. Fine. Restrucuturing that was easy. It now reflects some sense of
order.

Near the boundaries, I really want LocalLink interfaces, not the constituent
signals. Internally, I want modules to pass around their own private types.
Cool. That really helps clean up the module boundaries. That wrong clock
problem was a big indicator of poor partitioning.

So, I really want to standardize interfaces between modules. Say, a
LocalLink port, rather than exposing the constituent signals. One module has
a LocalLink_out port. Another module has a LocalLink_in port. In between,
they connect with a LocalLink_x signal. I think that works... I just need 3
record types.

Any smooshing, smushing, and reshaping into SLV can be rather well
contained, if it's needed at all. I like that. They're neatly contained in
the module that. If they're needed.

What if I wanted a LocalLink of MyPrivateDataType? Record types have to be
fully constrained, and don't support generics. Does the concept stop here?
Do I need to manually rewrap each such type in its own type? I think I'll
just pass the data part separate from the LocalLink signals. I wish the
language supported more...

C++ generics are rather more round and whole in this sense. "template
<typename data_t> class LocalLink;", and "LocalLink<MyDataType>" connotes
exactly that. It doesn't seem worthwhile to create a VHDL module to just
marry the two together, so I expect I'll pass data separate from the control
sigs.
Other folks have asked you why you aren't just keeping your data in the
byte array form, but I'd ask the other question: Why try to keep your
data as a byte array rather than a wide data element like a
std_logic_vector(63 downto 0)? If you need it to have byte division
boundaries at different places at different times, then only break it
up where and as you need to. If you're thinking of it as "just a
collection of wires", then make it just a collection of wires.

Where do you draw the line between useful abstraction and unnecessary noise?

The wire protocol is byte oriented. The low 7 bits is data; the high bit
signifies if it is the last byte of this particular data word. The 7 bits of
data word are successively shifted into an accumulator until the high bit is
set. The value can be up to 56 bits wide, or 8 such 7-bit words. The value
lives in a 56 bit SLV (or, maybe more reasonably, an unsigned). Early in its
life, it suited me to treat the 56-bit accumulator as an array of 8 7-bit
words. Is this a useful abstraction? Or is it noise?

Internally, it kicks around the 56-bit value, along with the word count and
sign bit, which are needed elsewhere but not easily extracted from the
stored value. These are then wrapped and tagged with a 4-bit ID to signify
that it contains this type of value; other value types coexist. The 64-bit
mess gets flattened into a fifo, and then reconstituted when it comes out
the other end.

Since it passes through a phase when it is an SLV, is the abstraction
useful? Or just noise?

Similarly, the word width, 56, is largely set in stone by long industry
practice and also published standards. Is that data word an "slv(55 downto
0)", or is it really "unsigned(FF_VALUE_WIDTH-1 downto 0)"? Is this a useful
abtraction? Or is it noise?
 
M

MikeWhy

MikeWhy said:
So, I really want to standardize interfaces between modules. Say, a
LocalLink port, rather than exposing the constituent signals. One module
has a LocalLink_out port. Another module has a LocalLink_in port. In
between, they connect with a LocalLink_x signal. I think that works... I
just need 3 record types.

It turns out I don't know how to do this. The in/out'ness is specified for
the port, not in the record field.

Can this be done? The ports look like this:

entity foo_ll is
Port (
rst : in STD_LOGIC;
------------------
clk_src : in STD_LOGIC;
src_rdy_in_n : in STD_LOGIC;
dst_rdy_out_n : out STD_LOGIC;
din : in STD_LOGIC_VECTOR (7 downto 0);
sof_in_n : in STD_LOGIC;
eof_in_n : in STD_LOGIC;
------------------
clk_dst : in STD_LOGIC;
src_rdy_out_n : out STD_LOGIC;
dst_rdy_in_n : in STD_LOGIC;
dout : out STD_LOGIC_VECTOR (7 downto 0);
sof_out_n : out STD_LOGIC;
eof_out_n : out STD_LOGIC
------------------
);
end entity foo_ll;
------------------------------
For the concept to be useful, it needs to look like this:

entity foo2_ll is
Port (
rst : in STD_LOGIC;
------------------
in_ll : XXXX locallink_in;
out_ll : XXXX locallink_out;
d_in : in data_t;
d_out : out data_t;
);
end entity foo2_ll;
--------------------------------
Instantiation and connectivity:

signal from_ll, to_ll : locallink_sig;
signal data_in, data_out : data_t;

afoo2 : entity work.foo2_ll
port map (
rst => rst,
in_ll => in_ll,
out_ll => out_ll,
d_in => data_in;
d_out => data_out;
);
 
R

Rob Gaddi

It turns out I don't know how to do this. The in/out'ness is specified for
the port, not in the record field.

Can this be done? The ports look like this:

entity foo_ll is
Port (
rst : in STD_LOGIC;
------------------
clk_src : in STD_LOGIC;
src_rdy_in_n : in STD_LOGIC;
dst_rdy_out_n : out STD_LOGIC;
din : in STD_LOGIC_VECTOR (7 downto 0);
sof_in_n : in STD_LOGIC;
eof_in_n : in STD_LOGIC;
------------------
clk_dst : in STD_LOGIC;
src_rdy_out_n : out STD_LOGIC;
dst_rdy_in_n : in STD_LOGIC;
dout : out STD_LOGIC_VECTOR (7 downto 0);
sof_out_n : out STD_LOGIC;
eof_out_n : out STD_LOGIC
------------------
);
end entity foo_ll;
------------------------------
For the concept to be useful, it needs to look like this:

entity foo2_ll is
Port (
rst : in STD_LOGIC;
------------------
in_ll : XXXX locallink_in;
out_ll : XXXX locallink_out;
d_in : in data_t;
d_out : out data_t;
);
end entity foo2_ll;
--------------------------------
Instantiation and connectivity:

signal from_ll, to_ll : locallink_sig;
signal data_in, data_out : data_t;

afoo2 : entity work.foo2_ll
port map (
rst => rst,
in_ll => in_ll,
out_ll => out_ll,
d_in => data_in;
d_out => data_out;
);

When I design WISHBONE systems, I wind up with the following mess o'
stuff in a file called pkg_global.vhd:

constant WB_DATA_WIDTH : positive := 32;
constant WB_ADDR_WIDTH : positive := 15;
constant WB_SEL_SIZE : positive := 8;
constant WB_SEL_WIDTH : positive := WB_DATA_WIDTH / WB_SEL_SIZE;

subtype t_wb_data is std_logic_vector(WB_DATA_WIDTH-1 downto 0);
subtype t_wb_addr is unsigned(WB_ADDR_WIDTH-1 downto 0);
subtype t_wb_sel is std_logic_vector(WB_SEL_WIDTH-1 downto 0);

type t_wb_mosi is record
DAT : t_wb_data;
ADDR : t_wb_addr;
SEL : t_wb_sel;
WE : std_logic;
STB : std_logic;
CYC : std_logic;
end record;

type t_wb_miso is record
DAT : t_wb_data;
ACK : std_logic;
BERR : std_logic;
end record;

Each slave has a MOSI port coming in and a MISO port coming out. Each
master has a MISO port coming in and a MOSI port coming out.
 
M

MikeWhy

Rob Gaddi said:
Each slave has a MOSI port coming in and a MISO port coming out. Each
master has a MISO port coming in and a MOSI port coming out.

That'll work. Thanks. Separate them on signal direction, rather than
conceptual grouping. (It's still not entirely optimal, but certainly
pragmatic and far better than what I have.)
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,768
Messages
2,569,574
Members
45,048
Latest member
verona

Latest Threads

Top