もうちょっとなんとかSpinal HDLのわけわからん記述が読めるようになりたい: とりあえずRVC Alignerの部分で何とか理解できる部分はないだろうか。
Aligner Pluginをもうちょっと腰を据えて解析してみたいと思う。
RVC付きのXLEN=64でNaxRiscvを生成し直そうと思う。
NaxRiscv/src/main/scala/naxriscv/Gen.scala
// ramstyle = "MLAB, no_rw_check" object Gen64 extends App{ LutInputs.set(6) def plugins = { val l = Config.plugins( sideChannels = false, //WARNING, FOR TEST PURPOSES ONLY, turn to false for real stuff <3 xlen = 64, withRdTime = false, aluCount = 2, decodeCount = 2, withRvc = true, withDebug = false, withEmbeddedJtagTap = false, debugTriggers = 4, withFloat = false, withDouble = false, lqSize = 16, sqSize = 16, asic = false ) l.foreach{ case p : EmbeddedJtagPlugin => p.debugCd.load(ClockDomain.current.copy(reset = Bool().setName("debug_reset"))) case _ => } // Tweek.euWritebackAt(l, "ALU0", 1) // Tweek.euWritebackAt(l, "ALU1", 1) l }
キャッシュブロックが128ビットの場合、16ビットのRVCを検出すべき単位は以下の図のように8個に分割される。
NaxRiscvの実装ではキャッシュ・ブロックが64ビットなので、4つのブロック分割されるはずだ。
キャッシュ・ブロックのビット幅/16ビットなので、NaxRiscvではSLICE_COUNT=4
となるはずだ。
このときのslices
の作り方がまた良く分からないのだが、data
はWORD
と buffer.data
の concatenate、そしてこれをsubdivideIn
によりSLICE_WIDTH
ビットごとに切り分ける。
つまり、data
はSLICE_WIDTH
毎のSLICE_COUNT*2
個のデータ要素に分けられる。
val slices = new Area { val data = (WORD ## buffer.data).subdivideIn(SLICE_WIDTH bits) var carry = (isInputValid ? input(MASK_FRONT) | B(0)) ## buffer.mask //Which slice have valid data var remains = CombInit(carry) var used = B(0, SLICE_COUNT*2 bits) }
wire [127:0] _zz_AlignerPlugin_logic_slices_data_0; wire [15:0] AlignerPlugin_logic_slices_data_0; wire [15:0] AlignerPlugin_logic_slices_data_1; wire [15:0] AlignerPlugin_logic_slices_data_2; wire [15:0] AlignerPlugin_logic_slices_data_3; wire [15:0] AlignerPlugin_logic_slices_data_4; wire [15:0] AlignerPlugin_logic_slices_data_5; wire [15:0] AlignerPlugin_logic_slices_data_6; wire [15:0] AlignerPlugin_logic_slices_data_7; assign _zz_AlignerPlugin_logic_slices_data_0 = {AlignerPlugin_setup_s2m_Fetch_WORD,AlignerPlugin_logic_buffer_data}; assign AlignerPlugin_logic_slices_data_0 = _zz_AlignerPlugin_logic_slices_data_0[15 : 0]; assign AlignerPlugin_logic_slices_data_1 = _zz_AlignerPlugin_logic_slices_data_0[31 : 16]; assign AlignerPlugin_logic_slices_data_2 = _zz_AlignerPlugin_logic_slices_data_0[47 : 32]; assign AlignerPlugin_logic_slices_data_3 = _zz_AlignerPlugin_logic_slices_data_0[63 : 48]; assign AlignerPlugin_logic_slices_data_4 = _zz_AlignerPlugin_logic_slices_data_0[79 : 64]; assign AlignerPlugin_logic_slices_data_5 = _zz_AlignerPlugin_logic_slices_data_0[95 : 80]; assign AlignerPlugin_logic_slices_data_6 = _zz_AlignerPlugin_logic_slices_data_0[111 : 96]; assign AlignerPlugin_logic_slices_data_7 = _zz_AlignerPlugin_logic_slices_data_0[127 : 112];
これらをデコーダを用いて展開していく。
val decoders = for (i <- 0 until SLICE_COUNT * 2) yield new Area { val rvc = RVC.get generate slices.data(i)(1 downto 0) =/= 3 val usage = if(RVC) { def mask16 = B(1 << i, SLICE_COUNT*2 bits) def mask32 = B(3 << i, SLICE_COUNT*2 bits) if(i == SLICE_COUNT*2 - 1) mask16 else rvc ? mask16 | mask32
RVCが有効で、当該16ビットがrvc
ブロックとして認識されればmask16
でマスクし、そうでなければ32ビット命令なので2ブロックぶんとしてマスクする。
assign AlignerPlugin_logic_decoders_0_usage = (AlignerPlugin_logic_decoders_0_rvc ? 8'h01 : 8'h03); assign AlignerPlugin_logic_decoders_1_usage = (AlignerPlugin_logic_decoders_1_rvc ? 8'h02 : 8'h06); assign AlignerPlugin_logic_decoders_2_usage = (AlignerPlugin_logic_decoders_2_rvc ? 8'h04 : 8'h0c); assign AlignerPlugin_logic_decoders_3_usage = (AlignerPlugin_logic_decoders_3_rvc ? 8'h08 : 8'h18); assign AlignerPlugin_logic_decoders_4_usage = (AlignerPlugin_logic_decoders_4_rvc ? 8'h10 : 8'h30); assign AlignerPlugin_logic_decoders_5_usage = (AlignerPlugin_logic_decoders_5_rvc ? 8'h20 : 8'h60); assign AlignerPlugin_logic_decoders_6_usage = (AlignerPlugin_logic_decoders_6_rvc ? 8'h40 : 8'hc0); assign AlignerPlugin_logic_decoders_7_usage = 8'h80;
notEnoughData
は、2キャッシュライン分のデータにおいて、
- 最上位がRVI(=32ビット)場合は、キャッシュラインを跨ぐのでデータが足りない。
- 真ん中のブロックの場合は、RVI(=32ビット)かつ上位キャッシュラインが無効
というときに成立する、というものだと思う。
val notEnoughData = RVC.get match { case true => if(i == SLICE_COUNT * 2 - 1) !rvc else if(i == SLICE_COUNT - 1) !rvc && (!MASK_FRONT.lsb || !isInputValid) else False case false => False }
次に pastPrediction
は2キャッシュラインのうち上位のキャッシュラインにおいて、WORD_BRANCH_SLICE
よりも大きさブロック位置であれば有効となる。これはどういうことだ?多分分岐命令の位置を予測しているのかな?
val pastPrediction = if(i <= SLICE_COUNT) False else WORD_BRANCH_VALID && U(i - SLICE_COUNT) > WORD_BRANCH_SLICE //TODO may use MASK_BACK instead for timings ? val usable = !notEnoughData && !pastPrediction }
assign FetchPlugin_stages_1_BtbPlugin_logic_ENTRY_slice = _zz_FetchPlugin_stages_1_BtbPlugin_logic_ENTRY_hash[17 : 16]; assign FetchPlugin_stages_1_Prediction_WORD_BRANCH_SLICE = FetchPlugin_stages_1_BtbPlugin_logic_ENTRY_slice;