vivado xsim 사용하기

2024. 8. 13. 00:59System Verilog

Cadence사의 Xcelium은 xmvlog, xmelab, xmsim을 xrun이라는 커맨드를 통해 한번에 실행할 수 있다. 

vivado로 Xcelium처럼 한번에 실행하는 Command는 찾지 못했다. 

 

#!/bin/bash 

./:del
xvlog -f file.f -sv
xelab tb
xsim  tb -t log_wave.tcl -wdb dump.wdb -view dump.wdb -gui

 

xvlog는 xrun -c 에 해당하며, Compile만 수행한다. -f은 filelist를 물려 돌리는 option, -sv는 systemverilog compile시 사용한다. -sv 옵션을 사용하지 않으면 일반 verilog로 인식해 문법 오류가 날 수 있다, 

 

xvlog와 xelab은 일반적인 시뮬레이터와 비슷하게 쓸 수 있었지만, xsim은 살짝 달랐다. 

 

xcelium에서는  -input option을 통해 tcl로 simulation을 실행하는 도중 명령을 추가로 내릴 수 있는데, vivado에서도 유사하게 사용할 수 있다. 

 

차이점은 simulation dump 파일이다. 

 

Cadence사의 simVison에서 사용하는 *.shm이나, Synopsys사의 Verdi에서 사용하는 *.fsdb처럼 $shm_open 또는 $fsdbdumpfile과 같이 *.wdb를 dump받을 수 있는 task가 없는 듯 했다. (아직 찾지 못함)

 

따라서 $dumpfile, $dumpvars를 이용해 dump를 만든 뒤 -wdb 옵션을 통해 wdb 파일을 만들어 줘야 한다. 

-wdb를 사용하지 않으면 work.lib.wdb라는 기본 파일이 만들어진다.

 

그리고, tcl을 통해서 어떤 signal을 wdb에 dump받을지 정해줘야 한다. option은 다음과 같다.

 

• All signals in the design (excluding those of alternate top modules):

log_wave -r /

• All signals in a scope: /tb:

log_wave /tb/*

• Those objects having names that start with a and end in b and have digits in between:

log_wave [get_objects -regexp {^a[0-9]+b$}]

• All objects in the current scope and all child scopes, recursively:

log_wave -r *

• The objects in the current scope:

log_wave *

• Only the ports of the scope /tb/UUT, use the command:

log_wave [get_objects -filter {type == in_port || type == out_port || type == inout_port || type == port} /tb/UUT/*]

• Only the internal signals of the scope /tb/UUT, use the command:

log_wave [get_objects -filter {type == signal} /tb/UUT/*]

 

이 때 주의할 점은 $dump를 받지 않으면 dump를 찾을 수 없다며 오류가 나는데, $dumpfiles를 통해 만들어진 vcd를 기반으로 wdb를 만들어 주는 듯하다. 확실하진 않다.

 

-view 와 -gui는 같이 쓰는것을 권장한다 하며, -view는 *.wdb를 지정한다. 여러개의 wdb가 있으면 wdb개수만큼 해당 옵션을 넣어줘야한다. 

Vivado Design Suite Tcl Command Reference Guide 에 사용하는 방법이 자세히 나와있다.

위 3개의 command를 쭉 실행하면 다음과 같이 친숙한 vivado의 simulator화면이 켜진다. 

 

해당 코드는 RISC-V의 register file을 test하기 위해 작성한 것이다.

 

module riscv_rf #(parameter WD = 32)(
    input             i_clk      ,
	input             i_rstn     ,
	input  [WD-1 : 0] i_data     ,
	input  [4 : 0]    i_RD       ,
	input  [4 : 0]    i_RS1      , //register source1
	input  [4 : 0]    i_RS2      , //register source2
	input             i_wen      ,
	output [WD-1 : 0] o_RS1_data ,
	output [WD-1 : 0] o_RS2_data 
);


localparam stack_address = 1024;

reg [WD-1 : 0] r_x [0 : 32-1];

integer i,j;
always @(posedge i_clk, negedge i_rstn) begin
  if(!i_rstn)begin
    for(i=0; i<32; i=i+1)begin
      if(i!=2)
	      r_x[i] <= #0.1 32'b0;
      else
	      r_x[i] <= #0.1 stack_address; //r_x[2]= stack pointer
		end
	end
	else if(i_wen)begin
    r_x[i_RD] <= #0.1 i_data;
  	end
	else begin
	for(j=0; j<32; j=j+1)begin
    r_x[j] <= #0.1 r_x[j];
  end
	end
end

assign o_RS1_data = r_x[i_RS1];
assign o_RS2_data = r_x[i_RS2];

endmodule

일반적으로 하드웨어 설계할 때 권장되는 방식은 아니지만, 32개의 register를 하나하나 다 치기 귀찮아서 for문과 memory를 사용하였다. 

module tb;


reg         clk, rstn, wen1, wen2;
reg  [31:0] data;
reg  [4:0]  RD1, RD2, RS1, RS2;
wire [31:0] RS1_data, RS2_data;

riscv_rf u_riscv_rf(
.i_clk       (  clk            ),
.i_rstn      (  rstn           ),
.i_data      (  data         ),
.i_RD       (  RD1            ),
.i_RS1       (  RS1            ),
.i_RS2       (  RS2            ),
.i_wen      (  wen1           ),
.o_RS1_data  (  RS1_data       ),
.o_RS2_data  (  RS2_data       )
);

always #5 clk = !clk;

task write();
	for(int i = 0; i < 32; i++)begin
    RD1 = i;
		data= 32'h 10000000 + i;
		wen1 = 1'b1;
    #15;
	end

endtask 

task read();
  for(int i = 0; i < 15; i++)begin
    RS1 = i;
		RS2 = i + 16;
    #15;
  end 


endtask

initial begin
  $dumpfile("dump.vcd");
	$dumpvars(0, tb);

end
initial begin
clk      = 0;
rstn     = 0;
wen1     = 0;
wen2     = 0;
data    = 0;
RD1      = 0;
RD2      = 0;
RS1      = 0;
RS2      = 0;


#10 rstn = 1'b1;
write();
#100;
read();
$finish();

end


endmodule