Call execute not working in SAS - reference

data numbers;
input num;
datalines;
3
1
3
;
run;
%macro temp(num);
proc sql noprint;
select count(*) into :count from numbers;
quit;
%if (&num eq &count) %then
%put Match Found;
%else
%put No Match Found;
%symdel count;
%mend;
data _NULL_;
set numbers;
call execute('%temp('||num||')');
run;
Why this code is showing error though everything is correct.
I am checking the values in data set number to find match between
overall count with variable number (just for practice)
I got count using proc SQL, after checking the conditions I
am deleting the count macro variable. After
execution why SAS is giving the error message. Please explain whats
really happening in this code.
LOG-----when I execute the last data step--------->
6188 data _NULL_;
6189 set numbers;
6190 call execute('%temp('||num||')');
6191 run;
NOTE: Numeric values have been converted to character values at the places given by:
(Line):(Column).
6190:24
Match Found
WARNING: Apparent symbolic reference COUNT not resolved.
ERROR: A character operand was found in the %EVAL function or %IF condition where a numeric
operand is required. The condition was: &num eq &count
ERROR: The macro TEMP will stop executing.
WARNING: Apparent symbolic reference COUNT not resolved.
ERROR: A character operand was found in the %EVAL function or %IF condition where a numeric
operand is required. The condition was: &num eq &count
ERROR: The macro TEMP will stop executing.
NOTE: The SAS System stopped processing this step because of errors.
NOTE: There were 3 observations read from the data set WORK.NUMBERS.
NOTE: DATA statement used (Total process time):
real time 0.00 seconds
cpu time 0.00 seconds
NOTE: CALL EXECUTE generated line.
1 + proc sql;
1 + select count(*) into :count from numbers;
1 + quit;
NOTE: PROCEDURE SQL used (Total process time):
real time 0.09 seconds
cpu time 0.00 seconds
2 + proc sql;
2 + select count(*) into :count from numbers;
2 + quit;
NOTE: PROCEDURE SQL used (Total process time):
real time 0.15 seconds
cpu time 0.01 seconds
3 + proc sql;
3 + select count(*) into :count from numbers;
3 + quit;
NOTE: PROCEDURE SQL used (Total process time):
real time 0.15 seconds
cpu time 0.01 seconds

When the CALL EXECUTE() statement runs the code is pushed onto the stack. Since you did not use any macro quoting the macro is actually running and the generated code is pushed onto the stack. So the %IF statement is running before the SELECT statement has run. You can use %NRSTR() to delay this so that the macro call is pushed onto the stack.
call execute(cats('%nrstr(%temp)(',num,')'));
Or just avoid CALL EXECUTE and use a PUT statement to write the code to a file that you can %INCLUDE.
filename code temp;
data _null_;
set numbers;
file code ;
put '%temp(' num ')' ;
run;
%include code / source2 ;

Quoting from the documentation page for call execute:
Note: Because macro references execute immediately and SAS
statements do not execute until after a step boundary, you cannot use
CALL EXECUTE to invoke a macro that contains references for macro
variables that are created by CALL SYMPUT in that macro.
I think what you're trying to do here is essentially the same sort of thing, only using proc sql's select into rather than call symput within your macro.
One way around this limitation is to use the dosubl function. You can use this to create a function-style macro which you can then run via a %put statement generated by call execute:
data numbers;
input num;
datalines;
3
1
3
;
run;
%symdel COUNT;
%macro temp(num);
%sysfunc(dosubl(proc sql noprint;
select count(*) into :count from numbers;
quit;))
%if &num eq &count %then
%put Match Found;
%else
%put No Match Found;
%mend;
data _NULL_;
set numbers;
count = symget('Count');
put "Before call execute " _n_= count=;
call execute('%put rc=%temp('||num||');');
count = symget('Count');
put "After call execute " _n_= count=;
run;
Or alternatively, you can put the dosubl call in the data step rather than the macro definition:
%symdel COUNT;
%macro temp(num);
proc sql noprint;
select count(*) into :count from numbers;
quit;
%if &num eq &count %then
%put Match Found;
%else
%put No Match Found;
%mend;
data _NULL_;
set numbers;
count = symget('Count');
put "Before call execute " _n_= count=;
rc = dosubl('%temp('||num||')');
count = symget('Count');
put "After call execute " _n_= count=;
run;

Related

How can I give multiple input values in PLSQL using record type, object and constructor?

Could you help me to write a PLSQL code to pass 'multiple input values' for a single variable? Please help me how can I do it by using Record type, object and constructors to write Anonymous PLSQL block.
Thank You,
Regards,
Veeresh
Here is one way:
HR#XE> CREATE OR REPLACE TYPE NUM_ARRAY AS TABLE OF NUMBER;
2 /
Type created.
HR#XE> create or replace procedure demo_sp (
in_array in num_array,
out_sum out number
)
is
BEGIN
out_sum := 0;
for i in 1 ..in_array.count loop
out_sum := out_sum + in_array(i);
END LOOP;
END;
/
Procedure created.
HR#XE> DECLARE
total NUMBER;
nos NUM_ARRAY;
BEGIN
SELECT ROWNUM BULK COLLECT INTO nos FROM dual CONNECT BY ROWNUM<=5;
FOR I IN 1 .. nos.COUNT LOOP
dbms_output.put_line(nos(i));
end loop;
demo_sp(nos,total);
dbms_output.put_line('Total is: '||total);
END;
/
1
2
3
4
5
Total is: 15
PL/SQL procedure successfully completed.
HR#XE>

Get system time in VCS

Is there way to get system time in VCS/UVM ? I am looking for something similar to Perl's localtime(time). Is there way to print system time for every uvm_info printed ?
One way is to use $system() to run any system command, including system' date command.
initial
begin
$system("date");
end
From IEEE 1800 LRM:
$system makes a call to the C function system(). The C function
executes the argument passed to it as if the argument was executed
from the terminal. $system can be called as either a task or a
function. When called as a function, it returns the return value of
the call to system() with data type int. If $system is called with no
string argument, the C function system() will be called with the NULL
string.
Also, see here.
Depends on the version of VCS you are using. The latest version should support $system as defined in IEEE Std 1800-2012 § 20.18.1. Assuming you are running in a UNIX based environment you can do:
function string get_localtime();
int fd;
string localtime;
void'($system("data > localtime")); // temp file
fd = $fopen("localtime", "r");
void'($fscanf(fd,"%s",localtime));
$fclose(fd);
void'($system("rm localtime")); // delete file
return localtime;
endfunction
If your version VCS doesn't support $system or if your more conformable with C/C++, then use DPI (see IEEE Std 1800-2012 § 35). Create a function in C and compile it in VCS with your SystemVerilog files. In the SystemVerilog, add the import to allow access to your C function. Assuming your method name is my_localtime and and return time is a string, the import should look like:
import "DPI" function string my_localtime();
In c file wallclock.c :
#include <time.h>
wallclock() {
time_t t;
t = time(NULL);
return (ctime(&t));
//return time(NULL);
}
In SV file :
import "DPI-C" function string wallclock();
module try;
//int unsigned t;
string t;
initial begin
t = wallclock();
$write("time=%0s\n", t);
end
endmodule

Read binary file data in Verilog into 2D Array

I have an array that I want to load up from a binary file:
parameter c_ROWS = 8;
parameter c_COLS = 16;
reg [15:0] r_Image_Raw[0:c_ROWS-1][0:c_COLS-1];
My input file is binary data, 256 bytes long (same total space as r_Image_Raw). I tried using $fread to accomplish this, but it only works through the 4th column of the last row:
n_File_ID = $fopen(s_File_Name, "r");
n_Temp = $fread(r_Image_Raw, n_File_ID);
I also tried using $fscanf for this, but I get an error about packed types when opening the synthesis tool:
while (!$feof(n_File_ID))
n_Temp = $fscanf(n_File_ID, "%h", r_Image_Raw);
I feel like this should be easy to do. Do I have create a 2D for loop and loop through the r_Image_Raw variable, reading in 16 bits at a time? I feel like it should not be that complicated.
I realized my mistake. It should be:
n_File_ID = $fopen(s_File_Name, "rb");
n_Temp = $fread(r_Image_Raw, n_File_ID);
I was using "r" and not "rb" to specify that it was a binary file. Interestingly enough, "r" does work for the majority of the data, but it is unable read in the last ~13 locations from the file.
Try this.
f_bin = $fopen(s_File_Name,"rb");
for (r = 0; r < c_ROWS; r = r+1) begin
for (c = 0; c < c_COLS; c = c+1) begin
f = $fread(r16,f_bin);
r_Image_Raw[r][c] = r16;
end
end
See that $fread(r16,f_bin) first param is reg, second - file!
Below an example for reading from a binary file with systemverilog.
As shown in IEEE SV Standard documentation, the "nchar_code" will return the number of bytes/chars read. In case EOF have been already reached on last read this number will be zero.
Please, notice that "nchar_code" can be zero but EOF has not been reached, this happens if you have spaces or returns at the end of the data file.
You can control the number of bytes to be read with the $fread function. This is done with the type definition of the "data_write_temp" or "mem" of the below examples. If the "data_write_temp" variable is 16bits long then it will read 16bits each time the $fread is called. Besides, $fread will return "nchar_code=2" because 16bits are 2bytes. In case, "data_write_temp" is 32bits as in the example, the $fread will read nchar_code=4bytes(32bits). You can also define an array and the $fread function will try to fill that array.
Lets define a multidimensional array mem.
logic [31:0] mem [0:2][0:4][5:8];
In the example word contents, wzyx,
-w shows the start of the word
-z corresponds to words of the [0:2] dimension (3 blocks).
-y corresponds to words of the [0:4] dimension (5 rows).
-x corresponds to words of the [5:8] dimension (4 columns).
The file will be structure as below (notice #z shows the z dimension blocks):
#0 w005 w006 w007 w008
w015 w016 w017 w018
w025 w026 w027 w028
w035 w036 w037 w038
w045 w046 w047 w048
#1 w105 w106 w107 w108
w115 w116 w117 w118
w125 w126 w127 w128
w135 w136 w137 w138
w145 w146 w147 w148
#2 w205 w206 w207 w208
w215 w216 w217 w218
w225 w226 w227 w228
w235 w236 w237 w238
w245 w246 w247 w248
In the previous structure, the numbers shows the index of each dimension.
e.g. w048 means, the word w (32bits) value on index z =0, index y= 4 and index x= 8.
Now, you have many ways to read this.
You can read all in a single shot using the type "mem" declared above, or you can do a while loop until EOF reading pieces of 32bits using a "data_write_temp" variable of 32bits. The loop is interesting if you want to do something some checks for every word piece and you are not interested having a memory value.
In case multidimensional array / single shot read is chosen, then you can either use $fread or use an specific function $readmemh defined in SV standard.
$readmemh("mem.data", mem, 1, (3*5*4));
is equivalent to
$readmemh("mem.data", mem);
The $readmemh spare you the need to open/close the file.
If you use $fread for one shot read
logic [31:0] mem [0:2][0:4][5:8];
register_init_id = $fopen("mem.data","rb");
nchar_code = $fread(mem, register_init_id);
if (nchar_code!=(3*5*4)*4)) begin
`uvm_error("do_read_file", $sformatf("Was not possible to read the whole expected bytes"));
end
$fclose(register_init_id);
In case you wanted to do a loop using 32b word read. Then see the following example.
The example uses the data which is read from the file to write to AHB Bus using an AHB Verification Component.
logic [31:0] data_write_temp;
...
//DO REGISTER FILE
register_init_id = $fopen("../../software/binary.bin","rb");
if (register_init_id==0) begin `uvm_error("do_read_file", $sformatf("Was not possible to open the register_init_id file")); end
count_32b_words=0;
while(!$feof(register_init_id)) begin
nchar_code = $fread(data_write_temp, register_init_id);
if ((nchar_code!=4)||(nchar_code==0)) begin
if (nchar_code!=0) begin
`uvm_error("do_read_file", $sformatf("Was not possible to read from file a whole 4bytes word:%0d",nchar_code));
end
end else begin
tmp_ahb_address = (pnio_pkg::conf_ahb_register_init_file_part1 + 4*count_32b_words);
data_write_temp = (data_write_temp << 8*( (tmp_ahb_address)%(DATAWIDTH/(8))));//bit shift if necessary not aligned to 4 bytes
`uvm_create_on(m_ahb_xfer,p_sequencer.ahb0_seqr);
assert(m_ahb_xfer.randomize(* solvefaildebug *) with {
write == 1;//perform a write
HADDR == tmp_ahb_address;
HSIZE == SIZE_32_BIT;
HBURST == HBURST_SINGLE;
HXDATA.size() == 1; //only one data for single bust
HXDATA[0] == data_write_temp;
}) else $fatal (0, "Randomization failed"); //end assert
`uvm_send(m_ahb_xfer);
count_32b_words++;
end //end if there is a word read
end //end while
$fclose(register_init_id);

TRANWRD doesn't replace properly

I was trying to progressively remove each /* */ comment pair on the same line by using the tranwrd function. However, the replacement doesn't happen for some reason.
Here's the code:
data _null_;
str="/* Comment 1 */ /* Comment 2 */ /* Comment 3 */ /* Comment 4 */";
do while(1);
startc=find(str,"/*");
endc=find(str,"*/");
put startc endc;
if startc = 0 then leave;
else do;
temp=substr(str,startc,endc-startc+2);
put "temp: " temp;
str=tranwrd(str,temp,"");
put "str: " str;
end;
end;
run;
The code goes into infinite loop because although temp gets the value of "/* Comment 1 */", TRANWRD is unable to make a replacement for some reason.
You need to TRIM the argument for finding (temp). Otherwise it has extraneous spaces on the end. Remember, string variables in SAS always have their full length - so if it is a 200 long string with "ABCDE" in it, it really is "ABCDE " (up to 200).
data _null_;
str="/* Comment 1 */ /* Comment 2 */ /* Comment 3 */ /* Comment 4 */";
do while(1);
startc=find(str,"/*");
endc=find(str,"*/");
put startc endc;
if startc = 0 then leave;
else do;
temp=substr(str,startc,endc-startc+2);
put "temp: |" temp "|";
str=tranwrd(str,trim(temp),"");
put "str: " str;
end;
end;
run;
See the | | around temp; it has at least one extra space around it. Your example was fortuitous in that you incorrectly added 2 to the length (should add one); since all of your temp arguments are identical in length this wouldn't have come up if you had added 1, but in a real world example this is presumably not the case.
Further, if you want it replaced with nothing, as opposed to a single space, you need to use TRANSTRN and TRIMN (which I think are 9.2+). The above code really replaces it with a single space. SAS does not have a concept of "", character null/missing is always " ". TRIMN and TRANSTRN allow you to make this replacement as a sort of workaround.

[Perl]: Not a CODE reference ... my 1st script

i am getting the following error when i am trying to run my 1st Perl script:
[id=0 # 0] : IP address "3.3.3.3" corresponds to device "core".
Thread 1 terminated abnormally: Not a CODE reference at ./dev_ithread.pl line 23.
[id=0 # 1] : IP address "5.5.5.5" corresponds to device "border".
Thread 2 terminated abnormally: Not a CODE reference at ./dev_ithread.pl line 23.
and here is what i have written so far
#!/usr/bin/perl
use strict ;
use warnings ;
use diagnostics ;
use threads ;
use Config ;
$Config{useithreads} || die("\n---> Please recompile Perl with \<ithreads\> included. \n") ;
# IP parameterization of network elements.
my %device_ip = (
"core" => "3.3.3.3",
"border" => "5.5.5.5",
) ;
# Initialize devices' pool of threads.
my $index = 0 ;
my #device_thread = () ;
while( my ($key, $value) = each %device_ip )
{
push( #device_thread, threads->new(\&thread_job($key, $device_ip{$key}, $index))->join ) ; $index = $index+1 ;
}
# Worker thread subroutine.
sub thread_job
{
my ($device, $ip, $index) = #_ ;
my $ithread = threads->tid() ;
print "[id=$ithread # $index] : IP address \"$ip\" corresponds to device \"$device\". \n" ;
}
i would be thankful, if someone could help me overcome this problem.
thank you.
The first argument to threads->new() must be a code reference or the name of a function. You are executing the function and try to take a code reference of the result (which is most likely a true value since that is what print returns), hence the error. I guess your call should be like this:
threads->new(\&thread_job, $key, $device_ip{$key}, $index)->join
\&thread_job($key, $device_ip{$key}, $index) doesn't do what you think it does: it runs thread_job(...) immediately, then produces a reference to its result. threads->new then tries to execute that reference inside the new thread, which doesn't work because it's not a reference to a sub.
You probably want to say sub { thread_job($key, $device_ip{$key}, $index) } instead. (Or #musiKk's version.)

Resources