Using burst_read/write with register model - system-verilog

I've a register space of 16 registers.
These are accessible through serial bus (single as well as burst).
I've UVM reg model defined for these registers.
However none of the reg model method supports burst transaction on bus.
As a workaround
I can declare memory model for same space and whenever I need burst access I use memory model but it seems redundant to declare 2 separate classes for same thing and this approach won't mirror register values correctly.
create a function which loops for number of bytes iterations and access registers one by one however this method doesn't create burst transaction on bus.
So I would like to know if there is a way to use burst_read and burst_write methods with register model. It would be nice if burst_read and burst_write support mirroring (current implementation doesn't support this) but if not I can use .predict and .set so its not big concern.
Or can I implement a method for register model easily to support burst operation.

I found this to help get you started:
http://forums.accellera.org/topic/716-uvm-register-model-burst-access/
The guy mentions using the optional 'extension' argument that read/write take. You could store the length of the burst length inside a container object (think int vs. Integer in Java) and then pass that as an argument when calling write() on the first register.
A rough sketch (not tested):
// inside your register sequence
uvm_queue #(int) container = new("container");
container.push_front(4);
start_reg.write(status, data, .extension(container));
// inside your adapter
function uvm_sequence_item reg2bus(const ref uvm_reg_bus_op rw);
int burst_len = 1;
uvm_reg_item reg_item = get_item();
uvm_queue #(int) extension;
if ($cast(extension, reg_item.extension))
burst_len = extension.pop_front();
// do the stuff here based on the burst length
// ...
endfunction
I've used uvm_queue because there isn't any trivial container object in UVM.

After combining opinions provided by Tudor and links in the discussion, here is what works for adding burst operation to reg model.
This implementation doesn't show all the code but only required part for adding burst operation, I've tested it for write and read operation with serial protocols (SPI / I2C). Register model values are updated correctly as well as RTL registers are updated.
Create a class to hold data and burst length:
class burst_class extends uvm_object;
`uvm_object_utils (....);
int burst_length;
byte data [$];
function new (string name);
super.new(name);
endfunction
endclass
Inside register sequence (for read don't initialize data)
burst_class obj;
obj = new ("burstInfo");
obj.burst_length = 4; // replace with actual length
obj.data.push_back (data1);
obj.data.push_back (data2);
obj.data.push_back (data3);
obj.data.push_back (data4);
start_reg.read (status,...., .extension(obj));
start_reg.write (status, ...., .extension (obj));
After successful operation data values should be written or collected in obj object
In adapter class (reg2bus is updated for write and bus2reg is updated for read)
All the information about transaction is available in reg2bus except data in case of read.
adapter class
uvm_reg_item start_reg;
int burst_length;
burst_class adapter_obj;
reg2bus implementation
start_reg = this.get_item;
adapter_obj = new ("adapter_obj");
if($cast (adapter_obj, start_reg.extension)) begin
if (adapter_obj != null) begin
burst_length = adapter_obj.burst_length;
end
else
burst_length = 1; /// so that current implementation of adapter still works
end
Update the size of transaction over here according to burst_length and assign data correctly.
As for read bus2reg needs to be updated
bus2reg implementation (Already has all control information since reg2bus is always executed before bus2reg, use the values captured in reg2bus)
According to burst_length only assign data to object passed though extension in this case adapter_obj

Related

How to get the read signals in the sequence from driver in pipeline style UVM?

I'm trying to get read data (HRDATA) from a driver in the sequence or test.
This is my driver:
////////// Pipelined UVM Driver //////////
class ahb_pipelined_driver extends uvm_driver #(ahb_seq_item);
`uvm_component_utils(ahb_pipelined_driver)
/// Virtual Interface
virtual ahb_interface ahb_if;
/// Constructor
....
function void build_phase(uvm_phase phase);
super.build_phase(phase);
...
endfunction
forever begin
ahb_if.HADDR <= req.HADDR;
ahb_if.HWRITE <= req.HWRITE;
ahb_if.HBURST <= req.HBURST;
req.HRDATA = ahb_if.HRDATA;
req.HRESP = ahb_if.HRESP;
ahb_if.HWDATA <= req.HWDATA;
end
// Return the Request as Response
seq_item_port.put(req);
end_tr(req);
end
endtask:
endclass:
I read the read data from req.HRDATA = ahb_if.HRDATA; in the driver, and I can check the value.
But, the problem is that I'd like to send read data to the sequence but this sequence immediately finished after I call seq_item_port.get() in the driver. So I can’t both wait for the read data to be available and have pipelined operation.
I want to send the read data to the sequence or test from the driver. How am I supposed to do that?
You are not following the recommended UVM approach.
The driver should not sample the "read" data and check it. You should create a UVM agent with a driver and a monitor. The monitor should sample the read data and send it to a scoreboard for checking.
I recommend that you change your driver such that it no longer tries to sample the read data. Then create a UVM monitor which collects all AHB transactions (reads and writes) and sends them to a scoreboard.
See also:
Basic UVM example
UVM AHB example on EDA Playground

How do I properly model internal memory in my serial flash device?

I am writing a model for a serial flash device. I wonder how do I best model the internal memory? I would like the memory size to be configurable by the user since I intend to reuse this model for different serial flashes. It also needs to retain its content upon reset while the rest of the logic is reset.
I tried using a statically allocated character buffer but that is not configurable and seems very inappropriate for such large sizes as my flash model. My flash model is 512 MB.
saved char flash_image[512 * 1024 * 1024];
Assuming this will go into Simics, it is best modeled with two subobjects, both of which are available in Simics Base.
The first is an image which stores the actual data, the second is a ram which is used to read/write into the image.
Both the image and the ram are connects using the init_as_subobj template which makes Simics automatically create them as sub-objects of the device.
They are placed in a group where we can add an init method, which sets default values for the required attributes of the subobjects; in this case the "size" of the image and the "image" of the ram.
The ram object also uses the map_target template, which gives us useful methods for reading and writing into the ram.
dml 1.4;
device sample_dev;
import "utility.dml";
import "simics/simulator-api.dml";
group flash is init {
param size = 0x1000;
method init() {
SIM_set_attribute_default(
ram.obj, "image", SIM_make_attr_object(image.obj));
SIM_set_attribute_default(
image.obj, "size", SIM_make_attr_uint64(size));
}
connect image is init_as_subobj {
param classname = "image";
}
connect ram is (init_as_subobj, map_target) {
param classname = "ram";
}
}
method write_test() {
try
flash.ram.write(0, 1, 42);
catch
log error: "write failed, it shouldn't!";
}
The size of the image is given a default value by a parameter in this example, but it can also be set or overridden by the user when creating the object, by assigning to the attribute flash.image.size, e.g. like this:
SIM_create_object("sample_dev", "dev", **{"flash.image.size": 0x2000})

B&R get drive serial number via MC_BR_GetHardwareInfo function block

I'm trying to retrieve the serial number from a drive using the MC_BR_GetHardwareInfo function block. Since the documentation lacks any kind of example code on this topic I'm getting nowhere.
Which information should I provide to the function block in order to get the desired serial number?
Below sample will crash in the PLC, probably because the function block requires certain pointers to be addressed:
MC_HARDWARE_INFO_REF hwinfo;
MC_BR_GetHardwareInfo(&hwinfo);
You are probably getting a page fault, because you provide the MC_BR_GetHardwareInfo function block (FUB) a wrong type, which leads to random behavior.
A function block is basically a function which requires a reference to a specific type as parameter. This type contains the actual in- and outputs which are used, internal state variables, etc. We need this, because of the synchronous execution of the code. This means unlike a function, you need to call a FUB until it is done.
Let's take a look to the help of the FUB:
Guid: 056444ea-2a15-4af6-a5ae-0675894b17d3
So the FUB needs a reference to the Axis object of which you want to know the HW info and an Execute command. It will give you some status bits, an error code and the actual data you want to have within the structure HardwareInfo of the type MC_HARDWARE_INFO_REF.
First we need to instantiate the FUB by create a variable of its type. We do this in the local *.var file of the task:
VAR
fbGetHwInfo : MC_BR_GetHardwareInfo := (0);
END_VAR
Then we call set the parameters of the FUB and call it, which might look like this:
void _CYCLIC ProgramCyclic(void)
{
//should be set by the application or in watch/monitor; now it only
//executes once
fbGetHwInfo.Execute = 1;
//reference to your axis object; when using a wizard the first axis
//will be gAxis01 on default
fbGetHwInfo.Axis = (UDINT)&gAxis01;
//call the FUB
MC_BR_GetHardwareInfo(&fbGetHwInfo);
if(fbGetHwInfo.Error == 1)
{
//TODO: errorhandling
}
else if(fbGetHwInfo.Done == 1)
{
//TODO use output
//fbGetHwInfo.HardwareInfo
}
}
typically you would do this in some statemachine. Also you probably have to wait until the network to the drive is initialized. You could check this with the MC_BR_ReadDriveStatus FUB. Just for testing it should be enough to wait for some seconds after reboot and set the Execute flag in monitor mode.

Cloning / Copying / Duplicating Streams in Lazarus

I developed a procedure that receives a TStream; but the basic type, to allow the sending of all the types of stream heirs.
This procedure is intended to create one thread to each core, or multiple threads. Each thread will perform detailed analysis of stream data (read-only), and as Pascal classes are assigned by reference, and never by value, there will be a collision of threads, since the reading position is intercalará.
To fix this, I want the procedure do all the work to double the last TStream in memory, allocating it a new variable. This way I can duplicate the TStream in sufficient numbers so that each thread has a unique TStream. After the end of the very thread library memory.
Note: the procedure is within a DLL, the thread works.
Note 2: The goal is that the procedure to do all the necessary service, ie without the intervention of code that calls; You could easily pass an Array of TStream, rather than just a TStream. But I do not want it! The aim is that the service is provided entirely by the procedure.
Do you have any idea how to do this?
Thank you.
Addition:
I had a low-level idea, but my knowledge in Pascal is limited.
Identify the object's address in memory, and its size.
create a new address in memory with the same size as the original object.
copy the entire contents (raw) object to this new address.
I create a pointer to TStream that point to this new address in memory.
This would work, or is stupid?? If yes, how to operate? Example Please!
2º Addition:
Just as an example, suppose the program perform brute force attacks on encrypted streams (just an example, because it is not applicable):
Scene: A 30GB file in a CPU with 8 cores:
1º - TMemoryStream:
Create 8 TMemoryStream and copy the entire contents of the file for each of TMemoryStreams. This will result in 240GB RAM in use simultaneously. I consider this broken idea. In addition it would increase the processing time to the point of fastest not use multithreading. I would have to read the entire file into memory, and then loaded, begin to analyze it. Broke!
 * A bad alternative to TMemoryStream is to copy the file slowly to TMemoryStream in lots of 100MB / core (800MB), not to occupy the memory. So each thread looks only 100MB, frees the memory until you complete the entire file. But the problem is that it would require Synchronize() function in DLL, which we know does not work out as I open question in Synchronize () DLL freezes without errors and crashes
2º - TFileStream:
This is worse in my opinion. See, I get a TStream, create 8 TFileStream and copy all the 30GB for each TFileStream. That sucks because occupy 240GB on disk, which is a high value, even to HDD. The read and write time (copy) in HD will make the implementation of multithreaded turns out to be more time consuming than a single thread. Broke!
Conclusion: The two approaches above require use synchronize() to queue each thread to read the file. Therefore, the threads are not operating simultaneously, even on a multicore CPU. I know that even if he could simultaneous access to the file (directly creating several TFileStream), the operating system still enfileiraria threads to read the file one at a time, because the HDD is not truly thread-safe, he can not read two data at the same time . This is a physical limitation of the HDD! However, the queuing management of OS is much more effective and decrease the latent bottleneck efficiently, unlike if I implement manually synchronize(). This justifies my idea to clone TStream, would leave with S.O. all the working to manage file access queue; without any intervention - and I know he will do it better than me.
Example
In the above example, I want 8 Threads analyze differently and simultaneously the same Stream, knowing that the threads do not know what kind of Stream provided, it can be a file Stream, a stream from the Internet, or even a small TStringStream . The main program will create only one Strean, and will with configuration parameters. A simple example:
TModeForceBrute = (M1, M2, M3, M4, M5...)
TModesFB = set of TModeForceBrute;
TService = record
  stream: TStream;
  modes: array of TModesFB;
end;
For example, it should be possible to analyze only the Stream M1, M2 only, or both [M1, M2]. The TModesFB composition changes the way the stream is analyzed.
Each item in the array "modes", which functions as a task list, will be processed by a different thread. An example of a task list (JSON representation):
{
  Stream: MyTstream,
  modes: [
    [M1, m5],
    [M1],
    [M5, m2],
    [M5, m2, m4, m3],
    [M1, m1, m3]
  ]
}
Note: In analyzer [m1] + [m2] <> [m1, m2].
In Program:
function analysis(Task: TService; maxCores: integer): TMyResultType; external 'mydll.dll';
In DLL:
// Basic, simple and fasted Exemple! May contain syntax errors or logical.
function analysis(Task: TService; maxCores: integer): TMyResultType;
var
i, processors : integer;
begin
processors := getCPUCount();
if (maxCores < processors) and (maxCores > 0) then
processors := maxCores;
setlength (globalThreads, processors);
for i := 0 to processors - 1 do
// It is obvious that the counter modes in the original is not the same counter processors.
if i < length(Task.modes) then begin
globalThreads[i] := TAnalusysThread.create(true, Task.stream, Task.modes[i])
globalThreads[i].start();
end;
[...]
end;
Note: With a single thread the program works beautifully, with no known errors.
I want each thread to take care of a type of analysis, and I can not use Synchronize() in DLL. Understand? There is adequate and clean solution?
Cloning a stream is code like this:
streamdest:=TMemoryStream.create;
streamsrc.position:=0;
streamdest.copyfrom(streamdest);
streamsrc.position:=0;
streamdest.position:=0;
However doing things over DLL borders is hard, since the DLL has an own copy of libraries and library state. This is currently not recommended.
I'm answering my question, because I figured that no one had a really good solution. Perhaps because there is none!
So I adapted the idea of Marco van de Voort and Ken White, for a solution that works using TMemoryStream with partial load in memory batch 50MB, using TRTLCriticalSection for synchronization.
The solution also contains the same drawbacks mentioned in addition 2; are they:
Queuing access to HDD is the responsibility of my program and not of the operating system;
A single thread carries twice the same data in memory.
Depending on the processor speed, it may be that the thread analyze well the fast 50MB of memory; On the other hand, to load memory can be very slow. That would make the use of multiple threads are run sequentially, losing the advantage of using multithreaded, because every thread are congested access to the file, running sequentially as if they were a single thread.
So I consider this solution a dirty solution. But for now it works!
Below I give a simple example. This means that this adaptation may contain obvious errors of logic and / or syntax. But it is enough to demonstrate.
Using the same example of the issue, instead of passing a current to the "analysis" is passed a pointer to the process. This procedure is responsible for making the reading of the stream batch 50MB in sync.
Both DLL and Program:
TLotLoadStream = function (var toStm: TMemoryStream; lot, id: integer): int64 of object;
TModeForceBrute = (M1, M2, M3, M4, M5...)
TModesFB = set of TModeForceBrute;
TaskTService = record
reader: TLotLoadStream; {changes here <<<<<<< }
modes: array of TModesFB;
end;
In Program:
type
{ another code here }
TForm1 = class(TForm)
{ another code here }
CS : TRTLCriticalSection;
stream: TFileStream;
function MyReader(var toStm: TMemoryStream; lot: integer): int64 of object;
{ another code here }
end;
function analysis(Task: TService; maxCores: integer): TMyResultType; external 'mydll.dll';
{ another code here }
implementation
{ another code here }
function TForm1.MyReader(var toStm: TMemoryStream; lot: integer): int64 of object;
const
lotSize = (1024*1024) * 50; // 50MB
var
ler: int64;
begin
result := -1;
{
MUST BE PERFORMED PREVIOUSLY - FOR EXAMPLE IN TForm1.create()
InitCriticalSection (self.CriticalSection);
}
toStm.Clear;
ler := 0;
{ ENTERING IN CRITICAL SESSION }
EnterCriticalSection(self.CS);
{ POSITIONING IN LOT OF BEGIN}
self.streamSeek(lot * lotSize, soBeginning);
if (lot = 0) and (lotSize >= self.stream.size) then
ler := self.stream.size
else
if self.stream.Size >= (lotSize + (lot * lotSize)) THEN
ler := lotSize
else
ler := (self.stream.Size) - self.stream.Position; // stream inicia em 0?
{ COPYNG }
if (ler > 0) then
toStm.CopyFrom(self.stream, ler);
{ LEAVING THE CRITICAL SECTION }
LeaveCriticalSection(self.CS);
result := ler;
end;
In DLL:
{ another code here }
// Basic, simple and fasted Exemple! May contain syntax errors or logical.
function analysis(Task: TService; maxCores: integer): TMyResultType;
var
i, processors : integer;
begin
processors := getCPUCount();
if (maxCores < processors) and (maxCores > 0) then
processors := maxCores;
setlength (globalThreads, processors);
for i := 0 to processors - 1 do
// It is obvious that the counter modes in the original is not the same counter processors.
if i < length(Task.modes) then begin
globalThreads[i] := TAnalusysThread.create(true, Task.reader, Task.modes[i])
globalThreads[i].start();
end;
{ another code here }
end;
In DLL Thread Class:
type
{ another code here }
MyThreadAnalysis = class(TThread)
{ another code here }
reader: TLotLoadStream;
procedure Execute;
{ another code here }
end;
{ another code here }
implementation
{ another code here }
procedure MyThreadAnalysis.Execute;
var
Stream: TMemoryStream;
lot: integer;
{My analyzer already all written using buff, the job of rewriting it is too large, then it is so, two readings, two loads in memory, as I already mentioned in the question!}
buf: array[1..$F000] of byte; // 60K
begin
lot := 0;
Stream := TMemoryStream.Create;
self.reader(stream, lot);
while (assigned(Stream)) and (Stream <> nil) and (Stream.Size > 0) then begin
Stream.Seek(0, soBeginning);
{ 2º loading to memory buf }
while (Stream.Position < Stream.Size) do begin
n := Stream.read(buf, sizeof(buf));
{ MY CODE HERE }
end;
inc(lot);
self.reader(stream, lot, integer(Pchar(name)));
end;
end;
So as seen this is a stopgap solution. I still hope to find a clean solution that allows me to double the flow controller in such a way that access to data is the operating system's responsibility and not my program.

Basic UVM sequence simulation query

I have a couple of issues with a basic UVM based TB I'm trying out to understand sequences and their working.
bvalid is being always picked as 0 in the driver when being updated in the response item
Couple of error messages for last 2 transactions (# UVM_ERROR # 18: uvm_test_top.axi_agent1.axi_base_seqr1##axi_base_seq1 [uvm_test_top.axi_agent1.axi_base_seqr1.axi_base_seq1] Response queue overflow, response was dropped)
Here is the link to the compiling code on EDA Playground
http://www.edaplayground.com/x/3x9
Any suggestions on what I'm missing??
Thanks
venkstart
Having a look at the specification for $urandom_range it shows the signature as: function int unsigned $urandom_range( int unsigned maxval, int unsigned minval = 0 ). Change your call to $urandom_range(1, 0) and it should work.
The second error comes from the fact that you are sending responses from the driver and not picking them up in your sequence. This is the line that does it: seq_item_port.item_done(axi_item_driv_src);. Either just do seq_item_port.item_done(); (don't send responses) or put a call to get_response() inside your sequence after finish_item(). What I usually do is update the fields of the original request and just call item_done(). For example, if I start a read transaction, in my driver I would drive the control signals and wait for the DUT to respond, update the data field of the request with the data I got from the DUT and call item_done() in my driver to mark the request as done. This way if I need this data in my sequence (to constrain some future item, for example) I have it.