FPGA開発日記

カテゴリ別記事インデックス https://msyksphinz.github.io/github_pages , English Version https://fpgadevdiary.hatenadiary.com/

LiteXによるSoC環境構築を試行する (シリアルコンソールの解析)

https://raw.githubusercontent.com/enjoy-digital/litex/master/doc/litex.png

まず、デザイン上にはシリアルコンソールとして以下のインタフェースが定義されている。

//------------------------------------------------------------------------------
// Module
//------------------------------------------------------------------------------

module sim (
    output wire          sim_trace,
    input  wire          sys_clk,
    output wire          serial_source_valid,
    input  wire          serial_source_ready,
    output wire    [7:0] serial_source_data,
    input  wire          serial_sink_valid,
    output wire          serial_sink_ready,
    input  wire    [7:0] serial_sink_data
);

おそらく、serial_sink_xxxがシリアルの入力で、serial_source_xxxがシリアルの出力になるのだろう。

このインタフェースは、以下のC言語の実装により接続されているように見える。

  • build/sim/gateware/sim_init.cpp
extern "C" void litex_sim_init(void **out)
{
...
    serial[0].signal = &sim->serial_source_valid;
    serial[1].signal = &sim->serial_source_ready;
    serial[2].signal = &sim->serial_source_data;
    serial[3].signal = &sim->serial_sink_valid;
    serial[4].signal = &sim->serial_sink_ready;
    serial[5].signal = &sim->serial_sink_data;
    litex_sim_register_pads(serial, (char*)"serial", 0);
}

この時の、litex_sim_register_pads()はLiteXのシミュレーション環境に、このシリアルデバイスをパッドとして登録する、ということだと思う。

おそらくこのイベントはserial2consoleが管理している。

  • litex/build/sim/core/modules/serial2console/serial2console.c
static int serial2console_add_pads(void *sess, struct pad_list_s *plist)
{
  int ret = RC_OK;
  struct session_s *s = (struct session_s*) sess;
  struct pad_s *pads;

  if(!sess || !plist) {
    ret = RC_INVARG;
    goto out;
  }
  pads = plist->pads;
  if(!strcmp(plist->name, "serial")) {
    litex_sim_module_pads_get(pads, "sink_data", (void**)&s->rx);
    litex_sim_module_pads_get(pads, "sink_valid", (void**)&s->rx_valid);
    litex_sim_module_pads_get(pads, "sink_ready", (void**)&s->rx_ready);
    litex_sim_module_pads_get(pads, "source_data", (void**)&s->tx);
    litex_sim_module_pads_get(pads, "source_valid", (void**)&s->tx_valid);
    litex_sim_module_pads_get(pads, "source_ready", (void**)&s->tx_ready);
  }

  if(!strcmp(plist->name, "sys_clk"))
    litex_sim_module_pads_get(pads, "sys_clk", (void**) &s->sys_clk);

out:
  return ret;
}

入力を受け付けるイベントの生成はこの辺。

static int serial2console_new(void **sess, char *args)
{
  int ret = RC_OK;
  struct timeval tv = {1, 0};
  struct session_s *s = NULL;

  if(!sess) {
    ret = RC_INVARG;
    goto out;
  }

  s = (struct session_s*) malloc(sizeof(struct session_s));
  if(!s) {
    ret=RC_NOENMEM;
    goto out;
  }
  memset(s, 0, sizeof(struct session_s));
  s->ev = event_new(base, fileno(stdin), EV_READ | EV_PERSIST , event_handler, s);
  event_add(s->ev, &tv);

out:
  *sess = (void*) s;
  return ret;
}

イベントハンドラにprintf()を入れてみても、正しく動作しているように見える。 そうすると、何かしらハードウェア側の動作がおかしい、ということになるか?

LiteXによるSoC環境構築を試行する (割込みの接続)

https://raw.githubusercontent.com/enjoy-digital/litex/master/doc/litex.png

PLICの実装を試している。自作CPUのSoC環境にPLICをインスタンスして接続する。

PLICの実装は、どこからかオープンソースのものを取ってこようと思う。

github.com

とりあえず以下のように接続する。

logic [ 7: 0] w_ie       [1];  // Interrupt enable per source, for each target
logic [ 2: 0] w_ipriority[8];  // Priority for each source (priority is a reserved keyword)
logic [ 2: 0] w_threshold[1];  // Priority Threshold for each target

logic [ 0: 0] w_int_req;
logic [ 3: 0] w_int_id[1];
logic [ 0: 0] w_int_claim;
logic [ 0: 0] w_int_complete;

assign w_ie[0] = {8{w_plic_if.ie}};
assign w_ipriority[0] = 3'h0;
assign w_ipriority[1] = 3'h0;
assign w_ipriority[2] = 3'h0;
assign w_ipriority[3] = 3'h0;
assign w_ipriority[4] = 3'h0;
assign w_ipriority[5] = 3'h0;
assign w_ipriority[6] = 3'h0;
assign w_ipriority[7] = 3'h0;
assign w_threshold[0] = 3'h0;

assign w_plic_if.int_id = w_int_id[0];


plic_core
  #(
    .SOURCES           (8), //Number of interrupt sources
    .TARGETS           (1), //Number of interrupt targets
    .PRIORITIES        (8), //Number of Priority levels
    .MAX_PENDING_COUNT (0)
    )
u_plic_core
  (
   .clk   (i_clk),     // Input: System clock
   .rst_n (i_reset_n), // Input: Active low asynchronous reset

   .src       (i_interrupts),           // Input: Interrupt request from devices/sources
   .el        (1'b0),                   // Input: Edge/Level sensitive for each source
   .ip        (w_plic_if.ip), // Output: Interrupt Pending for each source

   .ie        (w_ie),                   // Input: Interrupt enable per source, for each target
   .ipriority (w_ipriority),            // Input: Priority for each source (priority is a reserved keyword)
   .threshold (w_threshold),            // Input: Priority Threshold for each target

   .ireq      (w_plic_if.int_valid   ),         // Output: Interrupt request for each target
   .id        (w_int_id              ),         // Output: Interrupt ID (1..SOURCES), for each target
   .claim     (1'b0                  ),         // Input: Interrupt claim
   .complete  (w_plic_if.int_complete)          // Input: Interrupt handling complete
   );

ここで、claimポートについてはなんだかよく分からないのでとりあえず0に設定している。それ以外は、ピンをPLICとCPUの間で接続して、CSRに結び付けている。

PLICの割込みポートの接続は以下のようにする。

        # CPU Instance.
        self.cpu_params = dict(
            # Clk / Rst.
            i_i_clk       = ClockSignal("sys"),
            i_i_reset_n   = ~ResetSignal("sys") | self.reset,

            # Interrupts
            i_i_interrupts = self.interrupt,

これで一応Verilatorのコンパイルが通るようになったので、デバッグ開始だ。

RISC-Vの圧縮拡張命令Zc*拡張について

fprox.blogspot.com

RVC命令に追加して、新たに定義されているZc*拡張について調査する。

RISC-V Zc*拡張には、6種類の拡張命令があるらしい。

  • Zca: Zcf / Zcd以外の既存のRVC命令
  • Zcf: 既存の単精度RVCメモリアクセス命令
  • Zcd: 既存の倍精度RVCメモリアクセス命令
  • Zcb
  • Zcmp
  • Zcmt

Zcb拡張

Zcb拡張には12の命令が含まれている。

  • 5つのロード/ストア操作
  • 5つの符号/ゼロ拡張操作
  • 1つの乗算命令
  • 1つのビット/NOT命令

オペランドは3ビットのレジスタエンコーディングに限定され、(x8-x15)をターゲットにすることができる。 基本的にはすべての命令は32ビットの等価の命令に置き換えることができるが、Base ISAに変換されるというわけではない。たとえばBit Manipulationなどの命令に置き換わる可能性もある。

Zcbのロードストア命令は、既存のRVC拡張で定義されている32-bit/64-bitのメモリアクセス命令だけでなく、8-bit/16-bitのメモリアクセスもサポートしている。さらに、符号拡張またはゼロ拡張も定義されている。

Zcmp拡張

Zcmpは6つの新しい命令を導入している。 そのうち4つは関数読み出しと関数から戻るためのコードを小さくするように設計されている。 Push / Pop / PopRet / PopRetz命令により、スタックフレームを操作することができる。

残りの2つの命令はa0/a1レジスタと任意の場所のレジスタの値を移動することができる命令である。

Zcmt拡張

ジャンプテーブルに格納されているアドレスへのインデックス付きジャンプ。ジャンプターゲットは256エントリまで格納することができる。 これには、新たな制御レジスタであるJVTを使ってどこにテーブルが存在しているかの制御を行う。

sshfsにより2段階先のサーバのディスクを見る方法

要するに、~/.ssh/configに以下を追加しておく。

Host msyksphinz-ext-server
     HostName アクセスしたいサーバ
     User msyksphinz
     ProxyCommand ssh  <「アクセスしたいサーバにアクセスするためのログインサーバ」>  -W %h:%p

そして、以下のコマンドでアクセスしたいサーバの/home/msyksphinz/directoryが手元のマシンのdirectoryから見えるようになる。

sshfs msyksphinz-ext-server://home/msyksphinz/directory directory

LiteXによるSoC環境構築を試行する (割込み挿入方法)

https://raw.githubusercontent.com/enjoy-digital/litex/master/doc/litex.png

LiteXの続き。LiteXでどうにかBIOSを立ち上げることができるようになったが、次はキーボードの入力が受け付けられない。 これはおそらく割込みをまじめに接続していないことだろう。LiteXのほかのデザインはどうだろうか?

まずはRocketの実装とVexRiscVの実装を見てみる。

  • Rocketの場合
        self.cpu_params = dict(
            # Clk / Rst.
            i_clock = ClockSignal("sys"),
            i_reset = ResetSignal("sys") | self.reset,

            # Debug (ignored).
            i_debug_clock                         = 0,
            i_debug_reset                         = ResetSignal() | self.reset,
            o_debug_clockeddmi_dmi_req_ready      = Open(),
            i_debug_clockeddmi_dmi_req_valid      = 0,
            i_debug_clockeddmi_dmi_req_bits_addr  = 0,
            i_debug_clockeddmi_dmi_req_bits_data  = 0,
            i_debug_clockeddmi_dmi_req_bits_op    = 0,
            i_debug_clockeddmi_dmi_resp_ready     = 0,
            o_debug_clockeddmi_dmi_resp_valid     = Open(),
            o_debug_clockeddmi_dmi_resp_bits_data = Open(),
            o_debug_clockeddmi_dmi_resp_bits_resp = Open(),
            i_debug_clockeddmi_dmiClock           = 0,
            i_debug_clockeddmi_dmiReset           = ResetSignal() | self.reset,
            o_debug_ndreset                       = Open(),
            o_debug_dmactive                      = Open(),
            i_debug_dmactiveAck                   = 0,

            # IRQ.
            i_interrupts = self.interrupt,
  • VexRiscVの場合
        # CPU Instance.
        self.cpu_params = dict(
            i_clk                    = ClockSignal("sys"),
            i_reset                  = ResetSignal("sys") | self.reset,

            i_externalInterruptArray = self.interrupt,
            i_timerInterrupt         = 0,
            i_softwareInterrupt      = 0,

Interruptという信号を接続しているようだ。これは元をたどれば、タイマーとUARTに接続されているようだ。

always @(*) begin
    main_interrupt <= 32'd0;
    main_interrupt[1] <= main_timer_irq;
    main_interrupt[0] <= main_uart_irq;
end

さらにこれをPLICに接続することが前提のようだ。Rocket側の実装を追いかけると、PLICのラッパーに接続されているようだ。

  ClockSinkDomain plicDomainWrapper ( // @[Plic.scala 359:39]
    .auto_plic_int_in_0(plicDomainWrapper_auto_plic_int_in_0),
    .auto_plic_int_in_1(plicDomainWrapper_auto_plic_int_in_1),
    .auto_plic_int_in_2(plicDomainWrapper_auto_plic_int_in_2),
    .auto_plic_int_in_3(plicDomainWrapper_auto_plic_int_in_3),
    .auto_plic_int_in_4(plicDomainWrapper_auto_plic_int_in_4),
    .auto_plic_int_in_5(plicDomainWrapper_auto_plic_int_in_5),
    .auto_plic_int_in_6(plicDomainWrapper_auto_plic_int_in_6),
    .auto_plic_int_in_7(plicDomainWrapper_auto_plic_int_in_7),
    .auto_plic_int_out_0(plicDomainWrapper_auto_plic_int_out_0),
    .auto_plic_in_a_ready(plicDomainWrapper_auto_plic_in_a_ready),
    .auto_plic_in_a_valid(plicDomainWrapper_auto_plic_in_a_valid),
    .auto_plic_in_a_bits_opcode(plicDomainWrapper_auto_plic_in_a_bits_opcode),
    .auto_plic_in_a_bits_param(plicDomainWrapper_auto_plic_in_a_bits_param),
    .auto_plic_in_a_bits_size(plicDomainWrapper_auto_plic_in_a_bits_size),
    .auto_plic_in_a_bits_source(plicDomainWrapper_auto_plic_in_a_bits_source),
    .auto_plic_in_a_bits_address(plicDomainWrapper_auto_plic_in_a_bits_address),
    .auto_plic_in_a_bits_mask(plicDomainWrapper_auto_plic_in_a_bits_mask),
    .auto_plic_in_a_bits_data(plicDomainWrapper_auto_plic_in_a_bits_data),
    .auto_plic_in_a_bits_corrupt(plicDomainWrapper_auto_plic_in_a_bits_corrupt),
    .auto_plic_in_d_ready(plicDomainWrapper_auto_plic_in_d_ready),
    .auto_plic_in_d_valid(plicDomainWrapper_auto_plic_in_d_valid),
    .auto_plic_in_d_bits_opcode(plicDomainWrapper_auto_plic_in_d_bits_opcode),
    .auto_plic_in_d_bits_size(plicDomainWrapper_auto_plic_in_d_bits_size),
    .auto_plic_in_d_bits_source(plicDomainWrapper_auto_plic_in_d_bits_source),
    .auto_plic_in_d_bits_data(plicDomainWrapper_auto_plic_in_d_bits_data),
    .auto_clock_in_clock(plicDomainWrapper_auto_clock_in_clock),
    .auto_clock_in_reset(plicDomainWrapper_auto_clock_in_reset)
  );

VexRiscVの場合は、直接MEIPに接続されるようになっている。ちょっと強引かな。

    CsrPlugin_mip_MEIP <= externalInterrupt;
    CsrPlugin_mip_MTIP <= timerInterrupt;
    CsrPlugin_mip_MSIP <= softwareInterrupt;

PLICの実装は、どこからかオープンソースのものを取ってこようと思う。

github.com

RISC-V ベクトルプロセッサの実装論文Vitruvius+の論文を読む

RISC-Vのベクトル実装の論文を読んでいる。

https://dl.acm.org/doi/abs/10.1145/3575861

Vitruvius+というのはバルセロナスーパーコンピューティングセンターの開発しているRISC-Vベクトル拡張の実装で、初代Vitruviusの後継となる実装である。

ポイントだけまとめていく。

4 Vitruvius+ の優れた機能

以下、Vitruvius+が実装している優れた機能について説明する。これは、多くの最先端ソリューションと異なる点である。

  • メモリから算術演算へのベクトル命令アウトオブオーダーチェイニングの実装
  • 高速移動演算を導入することにより、ベクトル-ベクトル間の移動演算の実行を最適化。
  • インターレーンリングインターコネクトの再接続機能。
  • ベクトル削減演算の実行を高速化するための専用サポートを導入。

4.1 Vector Out-of-Order Chaining

  • ベクトルチェイニング: あるベクトル演算の結果を、それをソースオペランドとして使用する別の演算に転送すること
    • ある機能ユニットの演算結果として生成されたベクトルを、その演算の入力として使用する別の機能ユニットにバイパス実行させる (普通に考えればフォワーディング)
    • メモリから演算器へのベクトルチェーン
      • OVIでは、スカラーコアがベクターのロードオペレーションに代わってメモリにアクセスする仕様になっている。
        • このため、ベクトル要素の到着順序が予測できない。
      • 図 5 の VRF 構成
        • 例えばレーン 0が要素グループ40-72を受信した後、グループ 0-32 を受信する可能性がある
        • 要素グループの空き状況を把握し、少なくとも1つのグループが準備できている場合、それに依存する演算グループが開始できる
        • ベクトル要素グループの使用可能性は、レーン内のレディビットテーブルと呼ばれる特別な構造によって制御する
          • この構造は、各ベクトルレジスタの要素グループごとに1ビットで構成される
    - OVI規格の制限事項であるVPU側でのメモリ要求の受け付けができなくなるという問題を克服することができる
  • 図6は、アウトオブオーダーチェイニング
    • vle.vはv2レジスタに書き込む
    • vadd.vvはv2をソースベクトルオペランドとして使用する
    • ベクトルレジスタv2は、VRFに既に書き込まれている要素と、未定義の値xでマークされた未準備要素がある
    • グループ2は、サイクルTによってすべてのレーンがReady状態となるため、先に発行することができる。

Fast Moves

  • 表3は、算術命令の総数に対するvmvの数を示している
    • Jacobi-2DとStreamclusterでは、このタイプの命令が全体の算術演算のかなりの割合を占めている
  • 最適化を行わない場合
    • リネームにより1つの物理レジスタを消費し、VRFにアクセスしてソースベクターを読み、デスティネーションベクターに書き込む
    • 全体的な効果としては、あるベクトルのコピーを別のベクトルに作成する
  • Fast Move
    • リネームの際に完全に解決する
    • 図7: リネームユニットに2つの追加構造体を組み込んだ
      • Elements: ベクトル・レジスタが前回ベクトル演算の書き込み先となったときに割り当てられたベクトル長を記録している
      • 40個のエイリアスカウンターは、物理レジスタごとに1つずつあり、同じ物理レジスタが複数の論理レジスタに割り当てられた回数を記録している
      • 逆に、Fast Moveが実行されるたびに、1つの物理レジスタが複数の論理レジスタに関連付けられ、その物理レジスタにリネームされた高速移動の回数に応じてエイリアスカウンタが増加する。
      • 図7aのような初期状態を想定し、図示の順序で命令がリネームステージに入るものとする。
      • 図7bでは、vaddは、その宛先レジスタv28を物理レジスタvr32にリネームする
      • この命令はレーンに発行され、古い物理レジスタvr28はリタイア時に解放される。
      • 次に、図7cに示すように、vmvがリネームステージに入る。
        • この命令は、Fast Moveで実行する
        • 1サイクルでRATにアクセスし、v3v28に割り当てられた最後の物理レジスタ(それぞれvr3vr32)を読み出す
        • 次のサイクルでは、vr32v3に対応するRATエントリに書き込み、vr32エイリアスカウンタとエレメントテーブルの割り当てられたエレメントの新しい値を更新する
        • 次のサイクルでは、ベクトルレーンで実行する必要がないため、この命令は終了する。
        • これ以降、v3はベクトル長=13でvr32マッピングされる。
        • このように、Fast Move最適化により、vmvの実行レイテンシはわずか3サイクルに短縮され、VRFへの不要なアクセスを回避することで電力消費量も削減される
      • 同様に、図7dでは別の高速移動が実行されている
        • 処理は前述と同じで、vr32のエイリアスカウンタが2まで増加します。
        • ただし、今回は命令に付属するvlの値ではなく、v0のエレメントテーブルのエントリがv3からのエレメントで更新される点に注意すること
          • これは、マスクを正しく取り扱うようにするため
        • リネーム時の高速移動の実行時には、エイリアスとして使用する物理ベクターレジスタに最後に割り当てられた値とvlとの間で有効な要素の数が最も少ないものが選択される
        • このように、v0にアクセスする場合は、vl=13までしか有効な要素を読まないようにします。
        • 高速移動がROBからリタイアすると、エイリアスカウンタが減少する
      • 注: これもうちょっと考えて実行しないとv0の値が消えてしまいそうな…

サイクル精度シミュレータSniperのメモリリーク解析備忘録

サイクル精度シミュレータSniper魔改造したら大量にメモリリークするようになってしまったので、それを修正した備忘録。

問題は自分の修正した部分。ベクトル命令は1命令が複数の命令に分解される可能性がある。

これを単純に実装しようとすると、命令キャッシュは同じアドレスの命令を再利用するため、同じアドレスを指定する命令は常に同じ動きをすることになる。

LMUL=8の場合、Spikeを改造して8つの命令が同じアドレスで異なるアドレスを指定しながら動くように変更していた。これが問題の発端だ。

これをSniper側で処理する場合、命令キャッシュに命令が残っていると再利用されてしまうので、命令キャッシュと命令デコードキャッシュをいったんクリアして再デコードしていた。

これが問題で、命令デコード情報自体はクリアし切れずにどんどんメモリ上に残っていき、シミュレーションが進むとメモリを大量に消費するようになっていた。

これを解決するためにはいろいろ考えたのだが、結局命令キャッシュと命令デコードキャッシュを同じアドレスで最大8つまで保持できるようにした。デコード情報を追加する条件は、

  • ベクトル命令である
  • 命令キャッシュ中にまだ命令が存在していない(LMUL=0)
  • 前に実行した命令と同じ命令アドレスをデコードした (LMUL>1)

とすればよく、やや強引ではあるが最大で8つの独立したベクトル命令がデコードできるようになる。

実はこれは本質的な解決策ではないと思っていて、やはりSpike側で同一アドレスの命令を8つに分割してトレースを取るのは良い方法とは言えず、やはり1つの命令でまとめて管理すべきだった。

そして、Sniper側の命令デコードによってMicroOpを最大8つ生成し、それぞれで命令実行を管理するべきだった。こっちのほうがスマートだ。

ただしこれを実装するのにはまた時間がかかってしまうので、とりあえずの修正方法として、命令キャッシュの独立上書きをサポートするようにした。

diff --git a/common/trace_frontend/trace_thread.h b/common/trace_frontend/trace_thread.h
index 767dd92..0417844 100644
--- a/common/trace_frontend/trace_thread.h
+++ b/common/trace_frontend/trace_thread.h
@@ -71,6 +71,11 @@ class TraceThread : public Runnable
       bool m_cleanup;
       bool m_started;

+  uint64_t m_vec_last_pc_addr;
+  uint8_t  m_vec_lmul_idx;
+  std::unordered_map<IntPtr, std::vector<Instruction *>> m_vec_icache;
+  std::unordered_map<IntPtr, std::vector<const dl::DecodedInst *>> m_vec_decoder_cache;
+
       void run();
       static Sift::Mode __handleInstructionCountFunc(void* arg, uint32_t icount)
       { return ((TraceThread*)arg)->handleInstructionCountFunc(icount); }
@@ -47,6 +48,8 @@ TraceThread::TraceThread(Thread *thread, SubsecondTime time_start, String tracef
    , m_blocked(false)
    , m_cleanup(cleanup)
    , m_started(false)
+   , m_vec_last_pc_addr(0)
+   , m_vec_lmul_idx(0)
    , m_stopped(false)
 {

@@ -377,13 +380,22 @@ SubsecondTime TraceThread::getCurrentTime() const

 Instruction* TraceThread::decode(Sift::Instruction &inst)
 {
-
-   if (m_decoder_cache.count(inst.sinst->addr) != 0) {
-     delete m_decoder_cache[inst.sinst->addr];
+   if (m_decoder_cache.count(inst.sinst->addr) == 0) {
+     m_decoder_cache[inst.sinst->addr] = staticDecode(inst);
+     m_vec_decoder_cache[inst.sinst->addr].push_back(m_decoder_cache[inst.sinst->addr]);
+   }
+   bool is_vector = m_decoder_cache[inst.sinst->addr]->is_vector();
+   if (is_vector) {
+     if (m_vec_decoder_cache[inst.sinst->addr].size() <= m_vec_lmul_idx) {
+       m_vec_decoder_cache[inst.sinst->addr].push_back(staticDecode(inst));
+     }
+   } else {
+     if (m_vec_decoder_cache.count(inst.sinst->addr) == 0) {
+       m_vec_decoder_cache[inst.sinst->addr].push_back(staticDecode(inst));
+     }
    }
-   m_decoder_cache[inst.sinst->addr] = staticDecode(inst);

-   const dl::DecodedInst& dec_inst = *(m_decoder_cache[inst.sinst->addr]);
+   const dl::DecodedInst &dec_inst = *(m_vec_decoder_cache[inst.sinst->addr][m_vec_lmul_idx]);

    OperandList list;

@@ -643,12 +655,38 @@ void TraceThread::handleInstructionDetailed(Sift::Instruction &inst, Sift::Instr

// Set up instruction
if (m_icache.count(inst.sinst->addr) == 0) {
  • m_vec_last_pc_addr = 0;
  • m_vec_lmul_idx = 0; m_icache[inst.sinst->addr] = decode(inst);
  • m_vec_icache[inst.sinst->addr].push_back(m_icache[inst.sinst->addr]);
  • m_vec_last_pc_addr = inst.sinst->addr;
  • } else {
  • Instruction *existed_inst = m_icache[inst.sinst->addr];
  • const std::vector *uops = existed_inst->getMicroOps();
  • const MicroOp uop = (uops)[0];
  • if (uop->isVector()) {
  • if (inst.sinst->addr == m_vec_last_pc_addr) {
    
  •   m_vec_lmul_idx++;
    
  • } else {
    
  •   m_vec_lmul_idx = 0;
    
  • }
    
  • if (m_vec_icache[inst.sinst->addr].size() <= m_vec_lmul_idx + 1) {
    
  •   m_vec_icache[inst.sinst->addr].push_back (decode(inst));
    
  • }
    
  • m_vec_last_pc_addr = inst.sinst->addr;
    
  • } else {
  • m_vec_last_pc_addr = 0;
    
  • m_vec_lmul_idx = 0;
    
  • } } + // Here get the decoder instruction without checking, because we must have it for sure
  • const dl::DecodedInst &dec_inst = *(m_decoder_cache[inst.sinst->addr]);
  • // const dl::DecodedInst &dec_inst = *(m_decoder_cache[inst.sinst->addr]);
  • const dl::DecodedInst &dec_inst = *(m_vec_decoder_cache[inst.sinst->addr][m_vec_lmul_idx]); +
  • Instruction *ins = m_vec_icache[inst.sinst->addr][m_vec_lmul_idx];
  • const MicroOp uop_t = (ins->getMicroOps())[0];

  • Instruction ins = m_icache[inst.sinst->addr]; DynamicInstruction dynins = prfmdl->createDynamicInstruction(ins, va2pa(inst.sinst->addr));

    // Add dynamic instruction info