Shannon said:
I was specific about the ordering since I am going to use the flag
array to update a vector where the specific location of each bit is
important (and fixed). Something like:
Loop
reg_status(i) <= reg_status(i) or flag(i);
end loop
This flag array is a list of fault bits.
For processor register maps I prefer to define a record type like this...
type t_INPUT_DMA_STATUS_PORT is record
Done: std_ulogic_vector(31 downto 31);
Reserved: std_ulogic_vector(30 downto 26);
Current_Count: std_ulogic_vector(25 downto 0);
end record;
Then create a pair of functions to convert to and from std_ulogic_vectors
like this...
function To_Std_ULogic_Vector(L : t_INPUT_DMA_STATUS_PORT) return
std_ulogic_vector is
variable RetVal: std_ulogic_vector(31 downto 0);
begin
RetVal := (others => '0');
RetVal(L.Done'range) := L.Done;
RetVal(L.Reserved'range) := L.Reserved;
RetVal(L.Current_Count'range) := L.Current_Count;
return(RetVal);
end To_Std_ULogic_Vector;
function From_Std_ULogic_Vector(L : std_ulogic_vector) return
t_INPUT_DMA_STATUS_PORT is
variable RetVal: t_INPUT_DMA_STATUS_PORT;
variable Lx: std_ulogic_vector(L'length - 1 downto 0);
begin
Lx := L;
RetVal.Done := Lx(RetVal.Done'range);
RetVal.Reserved := Lx(RetVal.Reserved'range);
RetVal.Current_Count := Lx(RetVal.Current_Count'range);
return(RetVal);
end From_Std_ULogic_Vector;
All of this goes into a package that goes along with the entity.
Creating and maintaining these functions as the basic type evolves over time
is fairly straight forward, in fact changes that involves only the bits
within an existing field (like making a particular field be two bits wide
rather than one) involve editing only the type definition to put in the new
bit definitions, no other edits are needed, just recompile. Adding or
removing fields only requires editing the record and the to/from functions.
If there is any code anywhere else in the model that then would be affected
(such as X.Done <= "1" where the 'Done' field for whatever reason now
changes to be 2 bits) you don't need to hunt these affected areas down,
simply recompile and the compiler will flag it for you (in this case the
complaint being the assignment of a vector of size one to something that is
two bits wide)...much less tedious.
Having done that bit of groundwork (which can be somewhat or mostly
'macro-ized' depending on your fav editor's capabilities) lots of things
come out looking clean
Simple assignment:
Status.Done <= "1";
Logic operations (such as masking the bits of one reg with another) are
handled mostly by type conversion using the to/from functions:
Status <= From_Std_ULogic_Vector(To_Std_ULogic_Vector(Status) and
To_Std_ULogic_Vector(Mask_Reg));
When you get around to writing the testbench code for the processor model
these to/from functions will come in handy yet again since the model for the
'source code' of the processor will look clean dealing with the record types
(as above for 'simple assignment') but at some point the basic 'processor
model' itself will need to communicate via generic std_(u)logic_vectors.
That I generally wrap up inside a procedure (or function) in process scope
like this...
procedure Write_Port(Data: t_INPUT_DMA_STATUS_PORT)
Do that for each and every software port in your design and the testbench
processor model reduces down to a whole bunch of simple 'write_port' and
'read_port' calls without having to even specify addresses of those ports in
the call since those procedures can be written to contain the address by
virtue of VHDL's function/procedure overloading. As long as you only have
one port that is of type 't_INPUT_DMA_STATUS_PORT' the address of the port
can be defined as a constant in the procedure, so no need to pass it in as a
parameter as you would with a less general 'write anything' or 'read
anything' set of procedures. These 'register specific' read and write
procedures in turn generally call a more basic 'read' or 'write' procedure
that works with std_(u)logic_vectors that implements whatever the basic
protocol of a read or write actually is at the signal level (i.e. set strobe
low, wait 100 ns, drop 'wr_n'...blah, blah...) and works with any address
and data since it is working at the std_(u)logic_vector level.
Kevin Jennings