Vengineerの妄想(準備期間)

人生は短いけど、長いです。人生を楽しみましょう!

Xilinx ZynqMP SoC VIP の中を調べる(その5)

はじめに

Xilinx ZynqMP SoC VIP の中を調べる(その5)

今回は、前回でてきた zynq_ultra_ps_e_vip_v1_0_12_apis.sv の中をもう少し見ていきます。

このファイルは、

data/ip/xilinx/zynq_ultra_ps_e_vip_v1_0

にあります。

read_mem/wirte_mem API

read_mem/write_mem APIは、次のようになっています。

  task automatic read_mem;
      input [addr_width-1:0] start_addr;
      input [max_burst_bytes_width :0] no_of_bytes;
      output[max_burst_bits-1 :0] data;
      reg [1:0] mem_type;
      integer succ;
      begin
        mem_type = decode_address(start_addr);
        if(check_addr_aligned(start_addr)) begin    
          case(mem_type)
          OCM_MEM : begin
                    ocmc.ocm.read_mem(data,(start_addr-ocm_start_addr),no_of_bytes); 
                    if(DEBUG_INFO)
                      $display("[%0d] : %0s : Starting Address(0x%0h) -> Read %0d bytes of data from OCM Memory ",$time, DISP_INFO,  start_addr, no_of_bytes); 
                    end 
          DDR_MEM : begin
                    ddrc.ddr.read_mem(data,start_addr,no_of_bytes);
                    if(DEBUG_INFO)
                      $display("[%0d] : %0s : Starting Address(0x%0h) -> Read %0d bytes of data from DDR Memory",$time, DISP_INFO,  start_addr, no_of_bytes); 
                    end 
          default : begin
                    $display("[%0d] : %0s : Address(0x%0h) is out-of-range. 'read_mem' call failed ...\n",$time, DISP_ERR,  start_addr); 
                    if(STOP_ON_ERROR) $stop; 
                    end 
          endcase
        end else begin 
          $display("[%0d] : %0s : Address(0x%0h) has to be 32-bit aligned. 'read_mem' call failed ...",$time, DISP_ERR, start_addr);
          if(STOP_ON_ERROR)
            $stop; 
        end
      end
  endtask

  /* API for backdoor write to memories (DDR/OCM) */
  task automatic write_mem;
    input [max_burst_bits-1 :0] data;
    input [addr_width-1:0] start_addr;
    input [max_burst_bytes_width:0] no_of_bytes;
    reg [1:0] mem_type;
    integer succ;
    begin
      mem_type = decode_address(start_addr);
      if(check_addr_aligned(start_addr)) begin
        case(mem_type)
        OCM_MEM : begin
                  ocmc.ocm.write_mem(data,(start_addr-ocm_start_addr),no_of_bytes,all_strb_valid);
                  if(DEBUG_INFO)
                    $display("[%0d] : %0s : Starting Address(0x%0h) -> Write %0d bytes of data to OCM Memory",$time, DISP_INFO,  start_addr, no_of_bytes);
                  end
        DDR_MEM : begin
                  ddrc.ddr.write_mem(data,start_addr,no_of_bytes,all_strb_valid);
                  if(DEBUG_INFO)
                    $display("[%0d] : %0s : Starting Address(0x%0h) -> Write %0d bytes of data to DDR Memory",$time, DISP_INFO,  start_addr, no_of_bytes);
                  end
        default : begin
                  $display("[%0d] : %0s : Address(0x%0h) is out-of-range. 'write_mem' call failed ...\n",$time, DISP_ERR,  start_addr);
                  if(STOP_ON_ERROR) $stop;
                  end
        endcase
      end else begin
        $display("[%0d] : %0s : Address(0x%0h) has to be 32-bit aligned. 'write_mem' call failed ...",$time, DISP_ERR, start_addr);
        if(STOP_ON_ERROR)
          $stop;
      end
    end
  endtask

各引数のビット幅は、zynq_ultra_ps_e_vip_v1_0_12_local_params.sv の中で定義されています。

parameter addr_width = 40;   // maximum address width
parameter data_width = 32;   // maximum data width.

/* for internal read/write APIs used for data transfers */
parameter max_burst_len   = 256;  /// maximum brst length on axi 
parameter max_data_width  = 128; // maximum data width for internal AXI bursts 
parameter max_burst_bits  = (max_data_width * max_burst_len); // maximum data width for internal AXI bursts 
parameter max_burst_bytes = (max_burst_bits)/8;                // maximum data bytes in each transfer 
parameter max_burst_bytes_width = clogb2(max_burst_bytes); // maximum data width for internal AXI bursts 
parameter all_strb_valid = 2048'hFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF;

テストベンチの mpsoc_tb.v では、アドレスは32ビット、データも32ビットです。省略した部分は0になるので問題はなさそうですが。

        tb.mpsoc_sys.mpsoc_preset_i.zynq_ultra_ps_e_0.inst.write_data(32'hA0000000,4, 32'hFFFFFFFF, resp);
                #200
        tb.mpsoc_sys.mpsoc_preset_i.zynq_ultra_ps_e_0.inst.read_data(32'hA0000000,4, read_data, resp);
                #200

read_mem/write_mem API では、decode_address でアドレスのチェックを行っています。このAPIもこのファイル内で定義されています。

OCM_MEM/DDR_MEM/REG_MEM のアドレス空間で分けています。それ以外の時は、read_mem/write_mem API の中でエラーを表示し、STOP_ON_ERROR が ON の時は、$stop システムタスクにてシミュレータを止めています。

read_mem/write_mem API では、OCM_MEM と DDR_MEM の空間のみOKで、REG_MEM の空間にはアクセスできません。REG_MEMの空間には、read_register API にてアクセスすることになるようです。

  /* local API for address decoding*/
  function automatic [1:0] decode_address;
    input [addr_width-1:0] address;
    begin
      if(address >= ocm_start_addr && address <= ocm_end_addr )
        decode_address = OCM_MEM; /// OCM 
      else if(address >= ddr_start_addr && address <= ddr_end_addr)
        decode_address = DDR_MEM; /// DDR 
      else if(C_HIGH_DDR_EN && address >= high_ddr_start_addr)
        decode_address = DDR_MEM; /// DDR
      else if(address >= reg_start_addr && address <= reg_end_addr)
        decode_address = REG_MEM; /// Register Map
      else
        decode_address = INVALID_MEM_TYPE; /// ERROR in Address 
    end
  endfunction 

read_register API

REG_MEMの空間へは、read_register API でアクセスします。リードはこの read_regitser API でできますが、ライトはどうするのでしょうか?

 /* API to read single register */
 task read_register;
   input [addr_width-1:0] addr;
   output[data_width-1:0] data;
   begin
    if(check_addr_aligned(addr)) begin
       if(decode_address(addr) == REG_MEM) begin
         if(DEBUG_INFO)  $display("[%0d] : %0s : Reading Register (0x%0h) ",$time, DISP_INFO,  addr ); 
         regc.regm.get_data(addr >> 2, data);
         if(DEBUG_INFO)  $display("[%0d] : %0s : DONE -> Reading Register (0x%0h), Data returned(0x%0h)",$time, DISP_INFO,  addr, data ); 
       end else begin
         $display("[%0d] : %0s : Invalid Address(0x%0h) for Register Read. 'read_register' call failed ...",$time, DISP_ERR, addr);
       end
    end else begin
       data = 0;
       $display("[%0d] : %0s : Address(0x%0h) has to be 32-bit aligned. 'read_register' call failed ...",$time, DISP_ERR, addr);
    end

   end
 endtask

set_stop_on_error API

decode_address関数の中で、STOP_ON_ERROR という変数を使っていました。この変数は、set_stop_on_error API にて設定します。引数の LEVEL は 1ビットですので、STOP_ON_ERRORも1ビットです。

  /* API for setting the STOP_ON_ERROR*/  
  task automatic set_stop_on_error;
    input LEVEL;
    begin
      $display("[%0d] : %0s : Setting Stop On Error as %0b",$time, DISP_INFO, LEVEL);
      STOP_ON_ERROR = LEVEL;
     //  M_AXI_HPM0_FPD.master.set_stop_on_error(LEVEL);
     //  M_AXI_HPM1_FPD.master.set_stop_on_error(LEVEL);
     //  M_AXI_HPM0_LPD.master.set_stop_on_error(LEVEL);
     //  S_AXI_HPC0_FPD.slave.set_stop_on_error(LEVEL);
     //  S_AXI_HPC1_FPD.slave.set_stop_on_error(LEVEL);
     //  S_AXI_HP0_FPD.slave.set_stop_on_error(LEVEL);
     //  S_AXI_HP1_FPD.slave.set_stop_on_error(LEVEL);
     //  S_AXI_HP2_FPD.slave.set_stop_on_error(LEVEL);
     //  S_AXI_HP3_FPD.slave.set_stop_on_error(LEVEL);
     //  S_AXI_HPM0_LPD.slave.set_stop_on_error(LEVEL);
     //  S_AXI_ACP.slave.set_stop_on_error(LEVEL);
     //  S_AXI_ACE.slave.set_stop_on_error(LEVEL);
      M_AXI_HPM0_FPD.STOP_ON_ERROR = LEVEL;
      M_AXI_HPM1_FPD.STOP_ON_ERROR = LEVEL;
      M_AXI_HPM0_LPD.STOP_ON_ERROR = LEVEL;
      S_AXI_HPC0_FPD.STOP_ON_ERROR = LEVEL;
      S_AXI_HPC1_FPD.STOP_ON_ERROR = LEVEL;
      S_AXI_HP0_FPD.STOP_ON_ERROR = LEVEL;
      S_AXI_HP1_FPD.STOP_ON_ERROR = LEVEL;
      S_AXI_HP2_FPD.STOP_ON_ERROR = LEVEL;
      S_AXI_HP3_FPD.STOP_ON_ERROR = LEVEL;
      S_AXI_HPM0_LPD.STOP_ON_ERROR = LEVEL;
      S_AXI_ACP.STOP_ON_ERROR = LEVEL;
      S_AXI_ACE.STOP_ON_ERROR = LEVEL;

    end
  endtask 

por_srstv_reset / fpga_soft_reset API

この2つのAPIは、リセット関連のAPIです。VIP内の gen_rst インスタンスの同じAPIを呼び出しているだけです。

  /* API for por and strb reset control */
   task automatic por_srstb_reset;
     input por_reset_ctrl;
     begin
       if(DEBUG_INFO) $display("[%0d] : %0s : POR and STRB Reset called for 0x%0h",$time, DISP_INFO,  por_reset_ctrl); 
 //      gen_rst.por_srstb_reset(por_reset_ctrl); 
       gen_rst.por_srstb_reset(por_reset_ctrl);  
 
     end
   endtask

   /* API for soft reset control */
   task automatic fpga_soft_reset;
     input[data_width-1:0] reset_ctrl;
     begin
       if(DEBUG_INFO) $display("[%0d] : %0s : FPGA Soft Reset called for 0x%0h",$time, DISP_INFO,  reset_ctrl); 
       gen_rst.fpga_soft_reset(reset_ctrl);  
     end
   endtask

gen_rst は、 zynq_ultra_ps_e_vip_v1_0_vl_rfs.sv の11703行目に下記のようなモジュールのインスタンスとして宣言されています。

 zynq_ultra_ps_e_vip_v1_0_12_gen_reset gen_rst

zynq_ultra_ps_e_vip_v1_0_12_gen_reset は、zynq_ultra_ps_e_vip_v1_0_vl_rfs.sv の3952行目から定義があります。

下記のように、fpga_soft_reset API では、引数の4ビットを fabric_rst_n[3:0] に代入しています。

task fpga_soft_reset;
input[3:0] reset_ctrl;
 begin 
  fabric_rst_n[0] = reset_ctrl[0];
  fabric_rst_n[1] = reset_ctrl[1];
  fabric_rst_n[2] = reset_ctrl[2];
  fabric_rst_n[3] = reset_ctrl[3];
  
 end
endtask

fabric_rst_n[3:0] は、fclk_reset[3:0]_n に assign され、出力信号になっています。

assign fclk_reset0_n = !fabric_rst_n[0];
assign fclk_reset1_n = !fabric_rst_n[1];
assign fclk_reset2_n = !fabric_rst_n[2];
assign fclk_reset3_n = !fabric_rst_n[3];

一方、por_srstb_reset API では、下記のように、por_rst_n と sys_rst_n に引数の por_reset_ctrl を代入しています。

task por_srstb_reset;
input por_reset_ctrl;
 begin 
  por_rst_n = por_reset_ctrl;
  sys_rst_n = por_reset_ctrl;
 end
endtask

por_rst_n と sys_rst_n は、下記のように AND され、rst_out_n に assign されています。なんで、こんな変なことしているんでしょうかね。

assign rst_out_n = por_rst_n & sys_rst_n;

下記のように、rst_out_n は、net_rstn に、fclk_reset[3:0]_n はPL_RESET[3:0] に接続されています。

  zynq_ultra_ps_e_vip_v1_0_12_gen_reset gen_rst(
     .por_rst_n_dummy(PSS_ALTO_CORE_PAD_PORB),
     .sys_rst_n_dummy(PSS_ALTO_CORE_PAD_SRSTB),
     .rst_out_n      (net_rstn),
// 途中略
     .fclk_reset3_n  (PL_RESETN3),
     .fclk_reset2_n  (PL_RESETN2),
     .fclk_reset1_n  (PL_RESETN1),
     .fclk_reset0_n  (PL_RESETN0)
  );

net_rstn は、zynq_ultra_ps_e_vip_v1_0_vl_rfs.sv 内の

  • ddrc
  • ocmc
  • regc
  • ddr_interconnect
  • ocm_interconnect
  • reg_interconnect

のリセット信号に接続しています。

PL_RESETN[3:0] は、zynq_ultra_ps_e_vip_v1_0_12 の出力になっています。

おわりに

今回は、

  • read_mem / write_mem API
  • read_register API
  • set_stop_on_error API
  • por_srstv_reset / fpga_soft_reset API

について、見てみました。