RiscV ইন্টিগ্রেটেড ডিকোডার

এই টিউটোরিয়ালের উদ্দেশ্য হল:

  • জেনে নিন কিভাবে জেনারেট করা ISA এবং বাইনারি ডিকোডার একসাথে ফিট করে।
  • RiscV RV32I এর জন্য একটি সম্পূর্ণ নির্দেশনা ডিকোডার তৈরি করতে প্রয়োজনীয় C++ কোড লিখুন যা ISA এবং বাইনারি ডিকোডারকে একত্রিত করে।

নির্দেশনা ডিকোডার বুঝুন

নির্দেশনা ডিকোডার এর জন্য দায়ী, একটি নির্দেশনা ঠিকানা দেওয়া, মেমরি থেকে নির্দেশ শব্দটি পড়া এবং Instruction একটি সম্পূর্ণ প্রাথমিক উদাহরণ প্রদান করে যা সেই নির্দেশকে উপস্থাপন করে।

শীর্ষ স্তরের ডিকোডার নিম্নে দেখানো generic::DecoderInterface প্রয়োগ করে:

// This is the simulator's interface to the instruction decoder.
class DecoderInterface {
 public:
  // Return a decoded instruction for the given address. If there are errors
  // in the instruciton decoding, the decoder should still produce an
  // instruction that can be executed, but its semantic action function should
  // set an error condition in the simulation when executed.
  virtual Instruction *DecodeInstruction(uint64_t address) = 0;
  virtual ~DecoderInterface() = default;
};

আপনি দেখতে পাচ্ছেন, শুধুমাত্র একটি পদ্ধতি আছে যা বাস্তবায়ন করতে হবে: cpp virtual Instruction *DecodeInstruction(uint64_t address);

এখন দেখা যাক জেনারেট করা কোড দ্বারা কি প্রদান করা হয়েছে এবং কি কি প্রয়োজন।

প্রথমে, riscv32i_decoder.h ফাইলে শীর্ষ স্তরের ক্লাস RiscV32IInstructionSet বিবেচনা করুন, যা ISA ডিকোডারের টিউটোরিয়ালের শেষে তৈরি করা হয়েছিল। বিষয়বস্তু নতুন করে দেখতে, সেই টিউটোরিয়ালের সমাধান ডিরেক্টরিতে নেভিগেট করুন এবং সমস্ত পুনঃনির্মাণ করুন।

$ cd riscv_isa_decoder/solution
$ bazel build :all
...<snip>...

এখন আপনার ডিরেক্টরিকে রিপোজিটরি রুটে আবার পরিবর্তন করুন, তারপর উত্পন্ন উত্সগুলি একবার দেখে নেওয়া যাক। এর জন্য, ডাইরেক্টরিটিকে bazel-out/k8-fastbuild/bin/riscv_isa_decoder এ পরিবর্তন করুন (অন্য হোস্টের জন্য, k8-fastbuild হবে অন্য একটি স্ট্রিং)।

$ cd ../..
$ cd bazel-out/k8-fastbuild/bin/riscv_isa_decoder

আপনি তালিকাভুক্ত জেনারেটেড C++ কোড ধারণকারী চারটি উৎস ফাইল দেখতে পাবেন:

  • riscv32i_decoder.h
  • riscv32i_decoder.cc
  • riscv32i_enums.h
  • riscv32i_enums.cc

প্রথম ফাইলটি খুলুন riscv32i_decoder.h । তিনটি শ্রেণী রয়েছে যা আমাদের এক নজরে দেখতে হবে:

  • RiscV32IEncodingBase
  • RiscV32IInstructionSetFactory
  • RiscV32IInstructionSet

ক্লাসের নামকরণ নোট করুন। সেই ফাইলের "isa" ঘোষণায় দেওয়া নামের প্যাস্কাল-কেস সংস্করণের উপর ভিত্তি করে সমস্ত ক্লাসের নামকরণ করা হয়েছে: isa RiscV32I { ... }

প্রথমে RiscVIInstructionSet ক্লাস দিয়ে শুরু করা যাক। এটি নীচে দেখানো হয়েছে:

class RiscV32IInstructionSet {
 public:
  RiscV32IInstructionSet(ArchState *arch_state,
                         RiscV32IInstructionSetFactory *factory);
  Instruction *Decode(uint64 address, RiscV32IEncodingBase *encoding);

 private:
  std::unique_ptr<Riscv32Slot> riscv32_decoder_;
  ArchState *arch_state_;
};

এই ক্লাসে কোনও ভার্চুয়াল পদ্ধতি নেই, তাই এটি একটি স্বতন্ত্র ক্লাস, তবে দুটি জিনিস লক্ষ্য করুন। প্রথমত, কনস্ট্রাক্টর RiscV32IInstructionSetFactory ক্লাসের একটি উদাহরণে একটি পয়েন্টার নেয়। এটি একটি ক্লাস যা জেনারেট করা ডিকোডার RiscV32Slot ক্লাসের একটি উদাহরণ তৈরি করতে ব্যবহার করে, যা riscv32i.isa ফাইলে সংজ্ঞায়িত slot RiscV32 জন্য সংজ্ঞায়িত সমস্ত নির্দেশাবলী ডিকোড করতে ব্যবহৃত হয়। দ্বিতীয়ত, Decode পদ্ধতি RiscV32IEncodingBase এ টাইপ পয়েন্টারের একটি অতিরিক্ত প্যারামিটার নিয়ে যায়, এটি এমন একটি ক্লাস যা প্রথম টিউটোরিয়ালে তৈরি হওয়া isa ডিকোডার এবং দ্বিতীয় ল্যাবে তৈরি করা বাইনারি ডিকোডারের মধ্যে ইন্টারফেস প্রদান করবে।

ক্লাস RiscV32IInstructionSetFactory হল একটি বিমূর্ত শ্রেণী যেখান থেকে আমাদের সম্পূর্ণ ডিকোডারের জন্য আমাদের নিজস্ব বাস্তবায়ন করতে হবে। বেশিরভাগ ক্ষেত্রে এই ক্লাসটি তুচ্ছ: আমাদের .isa ফাইলে সংজ্ঞায়িত প্রতিটি স্লট ক্লাসের জন্য কনস্ট্রাক্টরকে কল করার জন্য শুধুমাত্র একটি পদ্ধতি প্রদান করুন। আমাদের ক্ষেত্রে, এটি খুবই সহজ কারণ এখানে শুধুমাত্র একটি একক শ্রেণী রয়েছে: Riscv32Slot ( Slot এর সাথে riscv32 নামের প্যাসকেস-কেস)। পদ্ধতিটি আপনার জন্য তৈরি করা হয়নি কারণ কিছু উন্নত ব্যবহারের ক্ষেত্রে রয়েছে যেখানে স্লট থেকে একটি সাবক্লাস বের করার এবং এর পরিবর্তে এর কনস্ট্রাক্টরকে কল করার উপযোগিতা থাকতে পারে।

আমরা এই টিউটোরিয়ালে পরে চূড়ান্ত ক্লাস RiscV32IEncodingBase মধ্য দিয়ে যাব, কারণ এটি আরেকটি অনুশীলনের বিষয়।


শীর্ষ স্তরের নির্দেশনা ডিকোডার সংজ্ঞায়িত করুন

ফ্যাক্টরি ক্লাস সংজ্ঞায়িত করুন

আপনি যদি প্রথম টিউটোরিয়ালের জন্য প্রকল্পটি পুনর্নির্মাণ করেন তবে নিশ্চিত করুন যে আপনি riscv_full_decoder ডিরেক্টরিতে ফিরে এসেছেন।

riscv32_decoder.h ফাইলটি খুলুন। সমস্ত প্রয়োজনীয় ফাইল ইতিমধ্যেই যোগ করা হয়েছে এবং নামস্থান সেট আপ করা হয়েছে৷

মন্তব্যটি চিহ্নিত করার পরে //Exercise 1 - step 1 RiscV32IsaFactory ক্লাস RiscV32IInstructionSetFactory থেকে উত্তরাধিকারসূত্রে সংজ্ঞায়িত করুন।

class RiscV32IsaFactory : public RiscV32InstructionSetFactory {};

এরপরে, CreateRiscv32Slot এর জন্য ওভাররাইড সংজ্ঞায়িত করুন। যেহেতু আমরা Riscv32Slot এর কোনো প্রাপ্ত ক্লাস ব্যবহার করি না, তাই আমরা std::make_unique ব্যবহার করে একটি নতুন উদাহরণ বরাদ্দ করি।

std::unique_ptr<Riscv32Slot> CreateRiscv32Slot(ArchState *) override {
  return std::make_unique<Riscv32Slot>(state);
}

আপনার যদি সাহায্যের প্রয়োজন হয় (অথবা আপনার কাজ পরীক্ষা করতে চান), সম্পূর্ণ উত্তর এখানে

ডিকোডার ক্লাস সংজ্ঞায়িত করুন

কনস্ট্রাক্টর, ডেস্ট্রাক্টর এবং পদ্ধতি ঘোষণা

পরবর্তী এটি ডিকোডার ক্লাস সংজ্ঞায়িত করার সময়। উপরের মতো একই ফাইলে, RiscV32Decoder এর ঘোষণায় যান। ঘোষণাটি একটি শ্রেণির সংজ্ঞায় প্রসারিত করুন যেখানে RiscV32Decoder generic::DecoderInterface থেকে উত্তরাধিকার সূত্রে প্রাপ্ত।

class RiscV32Decoder : public generic::DecoderInterface {
  public:
};

এর পরে, কনস্ট্রাক্টর লেখার আগে, বাইনারি ডিকোডারে আমাদের দ্বিতীয় টিউটোরিয়ালে তৈরি করা কোডটি দ্রুত দেখে নেওয়া যাক। সমস্ত Extract ফাংশন ছাড়াও, ফাংশন আছে DecodeRiscVInst32 :

OpcodeEnum DecodeRiscVInst32(uint32_t inst_word);

এই ফাংশনটি নির্দেশের শব্দটি নেয় যা ডিকোড করা প্রয়োজন এবং সেই নির্দেশের সাথে মেলে এমন অপকোড ফেরত দেয়। অন্যদিকে, DecodeInterface ক্লাস যা RiscV32Decoder প্রয়োগ করে শুধুমাত্র একটি ঠিকানায় পাস করে। এইভাবে, RiscV32Decoder ক্লাসকে DecodeRiscVInst32() এ পাস করার জন্য নির্দেশ শব্দটি পড়ার জন্য মেমরি অ্যাক্সেস করতে সক্ষম হতে হবে। এই প্রজেক্টে মেমরি অ্যাক্সেস করার উপায় হল .../mpact/sim/util/memory নামক util::MemoryInterface এ সংজ্ঞায়িত একটি সাধারণ মেমরি ইন্টারফেসের মাধ্যমে:

  // Load data from address into the DataBuffer, then schedule the Instruction
  // inst (if not nullptr) to be executed (using the function delay line) with
  // context. The size of the data access is based on size of the data buffer.
  virtual void Load(uint64_t address, DataBuffer *db, Instruction *inst,
                    ReferenceCount *context) = 0;

এছাড়াও আমাদের অন্যান্য ডিকোডার ক্লাসের কনস্ট্রাক্টরদের কাছে একটি state ক্লাস ইনস্ট্যান্স পাস করতে সক্ষম হতে হবে। উপযুক্ত স্টেট ক্লাস হল riscv::RiscVState ক্লাস, যা generic::ArchState থেকে প্রাপ্ত, RiscV-এর জন্য অতিরিক্ত কার্যকারিতা সহ। এর মানে আমাদের অবশ্যই কনস্ট্রাক্টর ঘোষণা করতে হবে যাতে এটি state এবং memory একটি পয়েন্টার নিতে পারে:

RiscV32Decoder(riscv::RiscVState *state, util::MemoryInterface *memory);

ডিফল্ট কনস্ট্রাক্টর মুছুন এবং ধ্বংসকারীকে ওভাররাইড করুন:

RiscV32Decoder() = delete;
~RiscV32Decoder() override;

পরবর্তীতে DecodeInstruction পদ্ধতিটি ঘোষণা করুন যা আমাদের generic::DecoderInterface থেকে ওভাররাইড করতে হবে।

generic::Instruction *DecodeInstruction(uint64_t address) override;

আপনার যদি সাহায্যের প্রয়োজন হয় (অথবা আপনার কাজ পরীক্ষা করতে চান), সম্পূর্ণ উত্তর এখানে


ডেটা সদস্য সংজ্ঞা

RiscV32Decoder ক্লাসে কনস্ট্রাক্টর প্যারামিটার এবং ফ্যাক্টরি ক্লাসের জন্য একটি পয়েন্টার সংরক্ষণ করার জন্য ব্যক্তিগত ডেটা সদস্যদের প্রয়োজন হবে।

 private:
  riscv::RiscVState *state_;
  util::MemoryInterface *memory_;

RiscV32IEncodingBase থেকে প্রাপ্ত এনকোডিং ক্লাসের জন্য এটির একটি পয়েন্টারও প্রয়োজন, আসুন এটিকে RiscV32IEncoding বলি (আমরা অনুশীলন 2 এ এটি বাস্তবায়ন করব)। উপরন্তু এটি RiscV32IInstructionSet এর একটি উদাহরণের জন্য একটি পয়েন্টার প্রয়োজন, তাই যোগ করুন:

  RiscV32IsaFactory *riscv_isa_factory_;
  RiscV32IEncoding *riscv_encoding_;
  RiscV32IInstructionSet *riscv_isa_;

অবশেষে, আমাদের মেমরি ইন্টারফেসের সাথে ব্যবহারের জন্য আমাদের একটি ডেটা সদস্যকে সংজ্ঞায়িত করতে হবে:

  generic::DataBuffer *inst_db_;

আপনার যদি সাহায্যের প্রয়োজন হয় (অথবা আপনার কাজ পরীক্ষা করতে চান), সম্পূর্ণ উত্তর এখানে

ডিকোডার ক্লাস পদ্ধতি সংজ্ঞায়িত করুন

এর পরে, কনস্ট্রাক্টর, ডেস্ট্রাক্টর এবং DecodeInstruction পদ্ধতি বাস্তবায়ন করার সময় এসেছে। riscv32_decoder.cc ফাইলটি খুলুন। খালি পদ্ধতিগুলি ইতিমধ্যে ফাইলের পাশাপাশি নেমস্পেস ঘোষণা এবং কয়েকটি using ঘোষণার মধ্যে রয়েছে।

কনস্ট্রাক্টর সংজ্ঞা

কনস্ট্রাক্টরকে শুধুমাত্র ডেটা সদস্যদের শুরু করতে হবে। প্রথমে state_ এবং memory_ শুরু করুন:

RiscV32Decoder::RiscV32Decoder(riscv::RiscVState *state,
                               util::MemoryInterface *memory)
    : state_(state), memory_(memory) {

পরবর্তী প্রতিটি ডিকোডার সম্পর্কিত ক্লাসের উদাহরণ বরাদ্দ করুন, উপযুক্ত পরামিতিগুলিতে পাস করুন।

  // Allocate the isa factory class, the top level isa decoder instance, and
  // the encoding parser.
  riscv_isa_factory_ = new RiscV32IsaFactory();
  riscv_isa_ = new RiscV32IInstructionSet(state, riscv_isa_factory_);
  riscv_encoding_ = new RiscV32IEncoding(state);

অবশেষে, DataBuffer উদাহরণ বরাদ্দ করুন। এটি state_ সদস্যের মাধ্যমে অ্যাক্সেসযোগ্য একটি কারখানা ব্যবহার করে বরাদ্দ করা হয়। আমরা একটি একক uint32_t সংরক্ষণ করার জন্য একটি ডেটা বাফার আকার বরাদ্দ করি, কারণ এটি নির্দেশ শব্দের আকার।

  inst_db_ = state_->db_factory()->Allocate<uint32_t>(1);

ধ্বংসকারী সংজ্ঞা

ধ্বংসকারী সহজ, শুধুমাত্র আমরা কন্সট্রাক্টরে বরাদ্দ করা বস্তুগুলিকে মুক্ত করুন, কিন্তু একটি মোচড় দিয়ে। ডেটা বাফার ইনস্ট্যান্স রেফারেন্স গণনা করা হয়, তাই সেই পয়েন্টারে delete কল করার পরিবর্তে, আমরা অবজেক্ট DecRef() :

RiscV32Decoder::~RiscV32Decoder() {
  inst_db_->DecRef();
  delete riscv_isa_;
  delete riscv_isa_factory_;
  delete riscv_encoding_;
}

পদ্ধতির সংজ্ঞা

আমাদের ক্ষেত্রে, এই পদ্ধতির বাস্তবায়ন বেশ সহজ। আমরা ধরে নেব যে ঠিকানাটি সঠিকভাবে সারিবদ্ধ করা হয়েছে এবং কোনও অতিরিক্ত ত্রুটি পরীক্ষা করার প্রয়োজন নেই৷

প্রথমত, মেমরি ইন্টারফেস এবং DataBuffer উদাহরণ ব্যবহার করে মেমরি থেকে নির্দেশ শব্দটি আনতে হবে।

  memory_->Load(address, inst_db_, nullptr, nullptr);
  uint32_t iword = inst_db_->Get<uint32_t>(0);

এর পরে, আমরা নির্দেশ শব্দটি পার্স করার জন্য RiscVIEncoding উদাহরণে কল করি, যা ISA ডিকোডারকে কল করার আগে করতে হবে। স্মরণ করুন যে ISA ডিকোডার নির্দেশ শব্দ দ্বারা নির্দিষ্ট অপকোড এবং অপারেন্ডগুলি পেতে সরাসরি RiscVIEncoding উদাহরণে কল করে। আমরা এখনও সেই ক্লাসটি প্রয়োগ করিনি, তবে আসুন সেই পদ্ধতি হিসাবে void ParseInstruction(uint32_t) ব্যবহার করি।

  riscv_encoding_->ParseInstruction(iword);

অবশেষে আমরা আইএসএ ডিকোডারকে কল করি, ঠিকানা এবং এনকোডিং ক্লাসে পাস করে।

  auto *instruction = riscv_isa_->Decode(address, riscv_encoding_);
  return instruction;

আপনার যদি সাহায্যের প্রয়োজন হয় (অথবা আপনার কাজ পরীক্ষা করতে চান), সম্পূর্ণ উত্তর এখানে


এনকোডিং ক্লাস

এনকোডিং ক্লাস একটি ইন্টারফেস প্রয়োগ করে যা ডিকোডার ক্লাস দ্বারা নির্দেশনা অপকোড, এর উত্স এবং গন্তব্য অপারেন্ড এবং রিসোর্স অপারেন্ডগুলি পেতে ব্যবহৃত হয়। এই অবজেক্টগুলি সবই নির্ভর করে বাইনারি ফরম্যাট ডিকোডার থেকে তথ্যের উপর, যেমন অপকোড, নির্দেশ শব্দের নির্দিষ্ট ক্ষেত্রের মান ইত্যাদি। এটিকে ডিকোডার ক্লাস থেকে আলাদা করা হয় যাতে এটি এনকোডিং অজ্ঞেয় থাকে এবং ভবিষ্যতে একাধিক ভিন্ন এনকোডিং স্কিমের জন্য সমর্থন সক্ষম করে। .

RiscV32IEncodingBase হল একটি বিমূর্ত শ্রেণী। আমাদের উদ্ভূত ক্লাসে আমাদের যে পদ্ধতিগুলি প্রয়োগ করতে হবে তার সেট নীচে দেখানো হয়েছে।

class RiscV32IEncodingBase {
 public:
  virtual ~RiscV32IEncodingBase() = default;

  virtual OpcodeEnum GetOpcode(SlotEnum slot, int entry) = 0;

  virtual ResourceOperandInterface *
              GetSimpleResourceOperand(SlotEnum slot, int entry, OpcodeEnum opcode,
                                       SimpleResourceVector &resource_vec, int end) = 0;

  virtual ResourceOperandInterface *
              GetComplexResourceOperand(SlotEnum slot, int entry, OpcodeEnum opcode,
                                        ComplexResourceEnum resource_op,
                                        int begin, int end) = 0;

  virtual PredicateOperandInterface *
              GetPredicate(SlotEnum slot, int entry, OpcodeEnum opcode,
                           PredOpEnum pred_op) = 0;

  virtual SourceOperandInterface *
              GetSource(SlotEnum slot, int entry, OpcodeEnum opcode,
                        SourceOpEnum source_op, int source_no) = 0;

  virtual DestinationOperandInterface *
              GetDestination(SlotEnum slot, int entry, OpcodeEnum opcode,
                             DestOpEnum dest_op, int dest_no, int latency) = 0;

  virtual int GetLatency(SlotEnum slot, int entry, OpcodeEnum opcode,
                         DestOpEnum dest_op, int dest_no) = 0;
};

প্রথম নজরে এটি কিছুটা জটিল দেখায়, বিশেষ করে প্যারামিটারের সংখ্যার সাথে, কিন্তু RiscV-এর মতো একটি সাধারণ আর্কিটেকচারের জন্য আমরা আসলে বেশিরভাগ প্যারামিটারকে উপেক্ষা করি, কারণ তাদের মানগুলি নিহিত থাকবে।

আসুন পালাক্রমে প্রতিটি পদ্ধতির মাধ্যমে যান।

OpcodeEnum GetOpcode(SlotEnum slot, int entry);

GetOpcode পদ্ধতি বর্তমান নির্দেশের জন্য OpcodeEnum সদস্যকে প্রদান করে, নির্দেশনা অপকোড সনাক্ত করে। OpcodeEnum ক্লাসটি তৈরি করা isa ডিকোডার ফাইল riscv32i_enums.h এ সংজ্ঞায়িত করা হয়েছে। পদ্ধতিটি দুটি পরামিতি নেয়, উভয়ই আমাদের উদ্দেশ্যে উপেক্ষা করা যেতে পারে। এর মধ্যে প্রথমটি হল স্লট টাইপ (একটি enum ক্লাস riscv32i_enums.h তেও সংজ্ঞায়িত করা হয়েছে), যেটি যেহেতু RiscV-এর শুধুমাত্র একটি স্লট রয়েছে, শুধুমাত্র একটি সম্ভাব্য মান রয়েছে: SlotEnum::kRiscv32 । দ্বিতীয়টি হল স্লটের দৃষ্টান্ত নম্বর (যদি স্লটের একাধিক উদাহরণ থাকে, যা কিছু VLIW আর্কিটেকচারে ঘটতে পারে)।

ResourceOperandInterface *
    GetSimpleResourceOperand(SlotEnum slot, int entry, OpcodeEnum opcode,
                                     SimpleResourceVector &resource_vec, int end)

ResourceOperandInterface *
    GetComplexResourceOperand(SlotEnum slot, int entry, OpcodeEnum opcode,
                                      ComplexResourceEnum resource_op,
                                      int begin, int end);

চক্র নির্ভুলতা উন্নত করার জন্য পরবর্তী দুটি পদ্ধতি প্রসেসরে হার্ডওয়্যার সংস্থান মডেল করার জন্য ব্যবহৃত হয়। আমাদের টিউটোরিয়াল অনুশীলনের জন্য, আমরা এগুলি ব্যবহার করব না, তাই বাস্তবায়নের সময়, nullptr ফেরত দিয়ে এগুলিকে স্টাব করা হবে।

PredicateOperandInterface *
            GetPredicate(SlotEnum slot, int entry, OpcodeEnum opcode,
                         PredOpEnum pred_op);

SourceOperandInterface *
            GetSource(SlotEnum slot, int entry, OpcodeEnum opcode,
                      SourceOpEnum source_op, int source_no);

DestinationOperandInterface *
            GetDestination(SlotEnum slot, int entry, OpcodeEnum opcode,
                           DestOpEnum dest_op, int dest_no, int latency);

এই তিনটি পদ্ধতি অপারেন্ড অবজেক্টে পয়েন্টার ফেরত দেয় যা নির্দেশের শব্দার্থিক ফাংশনের মধ্যে ব্যবহার করা হয় যেকোন নির্দেশের প্রেডিকেট অপারেন্ড, প্রতিটি নির্দেশের উৎস অপারেন্ডের মান অ্যাক্সেস করতে এবং নির্দেশনা গন্তব্য অপারেন্ডে নতুন মান লিখতে। যেহেতু RiscV নির্দেশের পূর্বাভাস ব্যবহার করে না, সেই পদ্ধতিতে শুধুমাত্র nullptr ফেরত দিতে হবে।

প্যারামিটারের প্যাটার্ন এই ফাংশন জুড়ে অনুরূপ। প্রথমে, GetOpcode এর মতো স্লট এবং এন্ট্রি পাস করা হয়। তারপর নির্দেশের জন্য opcode যার জন্য অপারেন্ড তৈরি করতে হবে। এটি শুধুমাত্র তখনই ব্যবহৃত হয় যখন বিভিন্ন অপকোডকে একই অপারেন্ড ধরনের জন্য বিভিন্ন অপারেন্ড অবজেক্ট ফেরত দিতে হয়, যা এই RiscV সিমুলেটরের ক্ষেত্রে নয়।

এর পরে রয়েছে প্রেডিকেট, সোর্স এবং গন্তব্য, অপারেন্ড গণনা এন্ট্রি যা অপারেন্ডটিকে চিহ্নিত করে যা তৈরি করতে হবে। এগুলি riscv32i_enums.h এর তিনটি OpEnums থেকে এসেছে যেমনটি নীচে দেখা গেছে:

  enum class PredOpEnum {
    kNone = 0,
    kPastMaxValue = 1,
  };

  enum class SourceOpEnum {
    kNone = 0,
    kBimm12 = 1,
    kCsr = 2,
    kImm12 = 3,
    kJimm20 = 4,
    kRs1 = 5,
    kRs2 = 6,
    kSimm12 = 7,
    kUimm20 = 8,
    kUimm5 = 9,
    kPastMaxValue = 10,
  };

  enum class DestOpEnum {
    kNone = 0,
    kCsr = 1,
    kNextPc = 2,
    kRd = 3,
    kPastMaxValue = 4,
  };

আপনি যদি riscv32.isa ফাইলের দিকে ফিরে তাকান, আপনি লক্ষ্য করবেন যে এগুলি প্রতিটি নির্দেশের ঘোষণায় ব্যবহৃত উত্স এবং গন্তব্য অপারেন্ড নামের সেটগুলির সাথে সামঞ্জস্যপূর্ণ। বিভিন্ন বিটফিল্ড এবং অপারেন্ড প্রকারের প্রতিনিধিত্ব করে এমন অপারেন্ডের জন্য বিভিন্ন অপারেন্ড নাম ব্যবহার করে, এটি এনকোডিং ক্লাস লেখাকে সহজ করে তোলে কারণ এনাম সদস্য অনন্যভাবে সঠিক অপারেন্ডের ধরণটি ফেরত দেওয়ার জন্য নির্ধারণ করে, এবং স্লট, এন্ট্রি, এর মান বিবেচনা করার প্রয়োজন নেই। অথবা অপকোড প্যারামিটার।

অবশেষে, উত্স এবং গন্তব্য অপারেন্ডের জন্য, অপারেন্ডের অর্ডিন্যাল অবস্থানটি পাস করা হয় (আবার, আমরা এটিকে উপেক্ষা করতে পারি), এবং গন্তব্য অপারেন্ডের জন্য, নির্দেশ জারি হওয়ার সময়ের মধ্যে বিলম্বিত হওয়া (চক্রে) এবং গন্তব্য ফলাফল পরবর্তী নির্দেশাবলী উপলব্ধ. আমাদের সিমুলেটরে, এই লেটেন্সি হবে 0, যার মানে নির্দেশনা রেজিস্টারে অবিলম্বে ফলাফল লিখে দেয়।

int GetLatency(SlotEnum slot, int entry, OpcodeEnum opcode,
                         DestOpEnum dest_op, int dest_no);

চূড়ান্ত ফাংশনটি একটি নির্দিষ্ট গন্তব্য অপারেন্ডের লেটেন্সি পেতে ব্যবহৃত হয় যদি এটি .isa ফাইলে * হিসাবে নির্দিষ্ট করা থাকে। এটি অস্বাভাবিক, এবং এই RiscV সিমুলেটরের জন্য ব্যবহার করা হয় না, তাই আমাদের এই ফাংশনের বাস্তবায়ন শুধুমাত্র 0 প্রদান করবে।


এনকোডিং ক্লাস সংজ্ঞায়িত করুন

হেডার ফাইল (.h)

পদ্ধতি

riscv32i_encoding.h ফাইলটি খুলুন। সমস্ত প্রয়োজনীয় ফাইল ইতিমধ্যেই যোগ করা হয়েছে এবং নামস্থান সেট আপ করা হয়েছে৷ মন্তব্য // Exercise 2.

চলুন শুরু করা যাক একটি ক্লাস RiscV32IEncoding যা জেনারেট করা ইন্টারফেস থেকে উত্তরাধিকারসূত্রে পাওয়া যায়।

class RiscV32IEncoding : public RiscV32IEncodingBase {
 public:

};

এর পরে, কনস্ট্রাক্টরকে স্টেট ইনস্ট্যান্সে একটি পয়েন্টার নিতে হবে, এই ক্ষেত্রে riscv::RiscVState এ একটি পয়েন্টার। ডিফল্ট ডেস্ট্রাক্টর ব্যবহার করা উচিত।

explicit RiscV32IEncoding(riscv::RiscVState *state);
~RiscV32IEncoding() override = default;

আমরা সমস্ত ইন্টারফেস পদ্ধতি যোগ করার আগে, নির্দেশটি পার্স করার জন্য RiscV32Decoder দ্বারা বলা পদ্ধতিতে যোগ করা যাক:

void ParseInstruction(uint32_t inst_word);

এরপরে, ব্যবহার করা হয় না এমন প্যারামিটারের নাম বাদ দেওয়ার সময় তুচ্ছ ওভাররাইড আছে এমন পদ্ধতিগুলি যোগ করা যাক:

// Trivial overrides.
ResourceOperandInterface *GetSimpleResourceOperand(SlotEnum, int, OpcodeEnum,
                                                   SimpleResourceVector &,
                                                   int) override {
  return nullptr;
}

ResourceOperandInterface *GetComplexResourceOperand(SlotEnum, int, OpcodeEnum,
                                                    ComplexResourceEnum ,
                                                    int, int) override {
  return nullptr;
}

PredicateOperandInterface *GetPredicate(SlotEnum, int, OpcodeEnum,
                                        PredOpEnum) override {
  return nullptr;
}

int GetLatency(SlotEnum, int, OpcodeEnum, DestOpEnum, int) override { return 0; }

পরিশেষে পাবলিক ইন্টারফেসের অবশিষ্ট মেথড ওভাররাইড যোগ করুন কিন্তু .cc ফাইলে স্থগিত বাস্তবায়নের সাথে।


OpcodeEnum GetOpcode(SlotEnum, int) override;

SourceOperandInterface *GetSource(SlotEnum , int, OpcodeEnum,
                                  SourceOpEnum source_op, int) override;

DestinationOperandInterface *GetDestination(SlotEnum, int, OpcodeEnum,
                                            DestOpEnum dest_op, int,
                                            int latency) override;

প্রতিটি অপারেন্ড গেটার পদ্ধতির বাস্তবায়নকে সহজ করার জন্য আমরা যথাক্রমে SourceOpEnum এবং DestOpEnum সদস্যদের সাংখ্যিক মান দ্বারা সূচীকৃত কলেবলের দুটি অ্যারে (ফাংশন অবজেক্ট) তৈরি করব। এইভাবে পদ্ধতিগুলির দেহগুলিকে এনাম মানের জন্য ফাংশন অবজেক্টকে কল করার মধ্যে হ্রাস করা হয় যা পাস করা হয় এবং এর রিটার্ন মান প্রদান করে।

এই দুটি অ্যারের প্রারম্ভিকতা সংগঠিত করার জন্য আমরা দুটি ব্যক্তিগত পদ্ধতি সংজ্ঞায়িত করি যেগুলি কনস্ট্রাক্টর থেকে নিম্নরূপ কল করা হবে:

 private:
  void InitializeSourceOperandGetters();
  void InitializeDestinationOperandGetters();

ডেটা সদস্য

প্রয়োজনীয় ডেটা সদস্য নিম্নরূপ:

  • state_ riscv::RiscVState * মান ধরে রাখতে।
  • inst_word_ টাইপের uint32_t যা বর্তমান নির্দেশ শব্দের মান ধরে রাখে।
  • opcode_ বর্তমান নির্দেশের অপকোড ধরে রাখতে যা ParseInstruction পদ্ধতি দ্বারা আপডেট করা হয়। এতে OpcodeEnum টাইপ আছে।
  • source_op_getters_ সোর্স অপারেন্ড অবজেক্ট পেতে ব্যবহৃত কলেবল সংরক্ষণ করার জন্য একটি অ্যারে। অ্যারের উপাদানগুলির ধরন হল absl::AnyInvocable<SourceOperandInterface *>()>
  • dest_op_getters_ গন্তব্য অপারেন্ড অবজেক্টগুলি পেতে ব্যবহৃত কলেবলগুলি সংরক্ষণ করার জন্য একটি অ্যারে। অ্যারের উপাদানগুলির ধরন হল absl::AnyInvocable<DestinationOperandInterface *>()>
  • xreg_alias RiscV পূর্ণসংখ্যা রেজিস্টার ABI নামগুলির একটি অ্যারে, যেমন, "x0" এবং "x1" এর পরিবর্তে "zero" এবং "ra"।

  riscv::RiscVState *state_;
  uint32_t inst_word_;
  OpcodeEnum opcode_;

  absl::AnyInvocable<SourceOperandInterface *()>
      source_op_getters_[static_cast<int>(SourceOpEnum::kPastMaxValue)];
  absl::AnyInvocable<DestinationOperandInterface *(int)>
      dest_op_getters_[static_cast<int>(DestOpEnum::kPastMaxValue)];

  const std::string xreg_alias_[32] = {
      "zero", "ra", "sp", "gp", "tp",  "t0",  "t1", "t2", "s0", "s1", "a0",
      "a1",   "a2", "a3", "a4", "a5",  "a6",  "a7", "s2", "s3", "s4", "s5",
      "s6",   "s7", "s8", "s9", "s10", "s11", "t3", "t4", "t5", "t6"};

আপনার যদি সাহায্যের প্রয়োজন হয় (অথবা আপনার কাজ পরীক্ষা করতে চান), সম্পূর্ণ উত্তর এখানে

উৎস ফাইল (.cc)।

riscv32i_encoding.cc ফাইলটি খুলুন। সমস্ত প্রয়োজনীয় ফাইল ইতিমধ্যেই যোগ করা হয়েছে এবং নামস্থান সেট আপ করা হয়েছে৷ মন্তব্য // Exercise 2.

হেল্পার ফাংশন

আমরা কয়েকটি সহায়ক ফাংশন লিখে শুরু করব যা আমরা উৎস এবং গন্তব্য রেজিস্টার অপারেন্ড তৈরি করতে ব্যবহার করি। এগুলি রেজিস্টারের ধরনে টেমপ্লেট করা হবে এবং রেজিস্টার অবজেক্টে একটি হ্যান্ডেল পেতে RiscVState অবজেক্টে কল করবে এবং তারপর রেজিস্টার অবজেক্টে একটি অপারেন্ড ফ্যাক্টরি পদ্ধতিতে কল করবে।

গন্তব্য অপারেন্ড সাহায্যকারী দিয়ে শুরু করা যাক:

template <typename RegType>
inline DestinationOperandInterface *GetRegisterDestinationOp(
    RiscVState *state, const std::string &name, int latency) {
  auto *reg = state->GetRegister<RegType>(name).first;
  return reg->CreateDestinationOperand(latency);
}

template <typename RegType>
inline DestinationOperandInterface *GetRegisterDestinationOp(
    RiscVState *state, const std::string &name, int latency,
    const std::string &op_name) {
  auto *reg = state->GetRegister<RegType>(name).first;
  return reg->CreateDestinationOperand(latency, op_name);
}

আপনি দেখতে পাচ্ছেন, দুটি সহায়ক ফাংশন রয়েছে। দ্বিতীয়টি একটি অতিরিক্ত পরামিতি op_name নেয় যা অপারেন্ডকে অন্তর্নিহিত রেজিস্টারের চেয়ে আলাদা নাম বা স্ট্রিং উপস্থাপনা করতে দেয়।

একইভাবে উত্স অপারেন্ড সাহায্যকারীদের জন্য:

template <typename RegType>
inline SourceOperandInterface *GetRegisterSourceOp(RiscVState *state,
                                                   const std::string &reg_name) {
  auto *reg = state->GetRegister<RegType>(reg_name).first;
  auto *op = reg->CreateSourceOperand();
  return op;
}

template <typename RegType>
inline SourceOperandInterface *GetRegisterSourceOp(RiscVState *state,
                                                   const std::string &reg_name,
                                                   const std::string &op_name) {
  auto *reg = state->GetRegister<RegType>(reg_name).first;
  auto *op = reg->CreateSourceOperand(op_name);
  return op;
}

কনস্ট্রাক্টর এবং ইন্টারফেস ফাংশন

কনস্ট্রাক্টর এবং ইন্টারফেস ফাংশন খুব সহজ. কনস্ট্রাক্টর অপারেন্ড গেটারের জন্য কলেবল অ্যারে শুরু করার জন্য দুটি প্রাথমিক পদ্ধতিকে কল করে।

RiscV32IEncoding::RiscV32IEncoding(RiscVState *state) : state_(state) {
  InitializeSourceOperandGetters();
  InitializeDestinationOperandGetters();
}

ParseInstruction নির্দেশ শব্দ এবং তারপর opcode সংরক্ষণ করে যা এটি বাইনারি ডিকোডার জেনারেটেড কোডে কল করার মাধ্যমে প্রাপ্ত করে।

// Parse the instruction word to determine the opcode.
void RiscV32IEncoding::ParseInstruction(uint32_t inst_word) {
  inst_word_ = inst_word;
  opcode_ = mpact::sim::codelab::DecodeRiscVInst32(inst_word_);
}

সবশেষে, অপারেন্ড গেটাররা গন্তব্য/সোর্স অপারেন্ড এনাম মান ব্যবহার করে অ্যারে লুকআপের উপর ভিত্তি করে গেটার ফাংশন থেকে মান ফেরত দেয়।


DestinationOperandInterface *RiscV32IEncoding::GetDestination(
    SlotEnum, int, OpcodeEnum, DestOpEnum dest_op, int, int latency) {
  return dest_op_getters_[static_cast<int>(dest_op)](latency);
}

SourceOperandInterface *RiscV32IEncoding::GetSource(SlotEnum, int, OpcodeEnum,
                                                    SourceOpEnum source_op, int) {
  return source_op_getters_[static_cast<int>(source_op)]();
}

অ্যারে আরম্ভ করার পদ্ধতি

আপনি অনুমান করতে পারেন, বেশিরভাগ কাজ গেটার অ্যারে শুরু করার জন্য, কিন্তু চিন্তা করবেন না, এটি একটি সহজ, পুনরাবৃত্তি প্যাটার্ন ব্যবহার করে সম্পন্ন করা হয়েছে। আসুন প্রথমে InitializeDestinationOpGetters() দিয়ে শুরু করি, যেহেতু শুধুমাত্র কয়েকটি গন্তব্য অপারেন্ড রয়েছে।

riscv32i_enums.h থেকে জেনারেট করা DestOpEnum ক্লাস রিকল করুন :

  enum class DestOpEnum {
    kNone = 0,
    kCsr = 1,
    kNextPc = 2,
    kRd = 3,
    kPastMaxValue = 4,
  };

dest_op_getters_ এর জন্য আমাদের 4টি এন্ট্রি শুরু করতে হবে, প্রতিটি kNone , kCsr , kNextPc এবং kRd এর জন্য একটি করে। সুবিধার জন্য, প্রতিটি এন্ট্রি একটি ল্যাম্বডা দিয়ে আরম্ভ করা হয়, যদিও আপনি কলেবলের অন্য কোনও ফর্মও ব্যবহার করতে পারেন। ল্যাম্বডার স্বাক্ষর void(int latency)

এখন পর্যন্ত আমরা MPACT-Sim-এ সংজ্ঞায়িত বিভিন্ন ধরনের গন্তব্য অপারেন্ড সম্পর্কে বেশি কথা বলিনি। এই অনুশীলনের জন্য আমরা শুধুমাত্র দুটি প্রকার ব্যবহার করব: generic::RegisterDestinationOperand register.h এ সংজ্ঞায়িত , এবং generic::DevNullOperand devnull_operand.h এ সংজ্ঞায়িত। এই অপারেন্ডের বিশদ বিবরণ এই মুহূর্তে সত্যিই গুরুত্বপূর্ণ নয়, শুধুমাত্র পূর্ববর্তীটি রেজিস্টারে লিখতে ব্যবহৃত হয় এবং পরবর্তীটি সমস্ত লেখাকে উপেক্ষা করে।

kNone এর জন্য প্রথম এন্ট্রিটি তুচ্ছ - শুধু একটি nullptr ফেরত দিন এবং ঐচ্ছিকভাবে একটি ত্রুটি লগ করুন।

void RiscV32IEncoding::InitializeDestinationOperandGetters() {
  // Destination operand getters.
  dest_op_getters_[static_cast<int>(DestOpEnum::kNone)] = [](int) {
    return nullptr;
  };

এর পরে আছে kCsr । এখানে আমরা একটু প্রতারণা করতে যাচ্ছি। "হ্যালো ওয়ার্ল্ড" প্রোগ্রামটি কোনো প্রকৃত CSR আপডেটের উপর নির্ভর করে না, তবে কিছু বয়লারপ্লেট কোড রয়েছে যা CSR নির্দেশাবলী কার্যকর করে। সমাধান হল "CSR" নামে একটি নিয়মিত রেজিস্টার ব্যবহার করে এটিকে ডামি করা এবং এই ধরনের সমস্ত লেখা এটিকে চ্যানেল করা।

  dest_op_getters_[static_cast<int>(DestOpEnum::kCsr)] = [this](int latency) {
    return GetRegisterDestinationOp<RV32Register>(state_, "CSR", latency);
  };

এরপরে আছে kNextPc , যা "pc" রেজিস্টারকে বোঝায়। এটি সমস্ত শাখা এবং লাফ নির্দেশের লক্ষ্য হিসাবে ব্যবহৃত হয়। নামটিকে RiscVStatekPcName হিসাবে সংজ্ঞায়িত করা হয়েছে।

  dest_op_getters_[static_cast<int>(DestOpEnum::kNextPc)] = [this](int latency) {
    return GetRegisterDestinationOp<RV32Register>(state_, RiscVState::kPcName, latency);
  }

অবশেষে kRd গন্তব্য অপারেন্ড আছে। riscv32i.isa তে অপারেন্ড rd শুধুমাত্র নির্দেশ শব্দের "rd" ক্ষেত্রে এনকোড করা পূর্ণসংখ্যা রেজিস্টারের উল্লেখ করার জন্য ব্যবহার করা হয়, তাই কোন অস্পষ্টতা নেই যা এটি নির্দেশ করে। শুধু একটি জটিলতা আছে। Register x0 (abi name zero ) 0 এ হার্ডওয়্যার করা হয়েছে, তাই সেই রেজিস্টারের জন্য আমরা DevNullOperand ব্যবহার করি।

তাই এই গেটারে আমরা প্রথমে .bin_fmt ফাইল থেকে উৎপন্ন Extract মেথড ব্যবহার করে rd ক্ষেত্রের মান বের করি। মান 0 হলে, আমরা একটি "DevNull" অপারেন্ড ফেরত দিই, অন্যথায় আমরা সঠিক রেজিস্টার অপারেন্ড ফেরত দিই, অপারেন্ড নাম হিসাবে উপযুক্ত রেজিস্টার উপনাম ব্যবহার করার যত্ন নিই।

  dest_op_getters_[static_cast<int>(DestOpEnum::kRd)] = [this](int latency) {
    // First extract register number from rd field.
    int num = inst32_format::ExtractRd(inst_word_);
    // For register x0, return the DevNull operand.
    if (num == 0) return new DevNullOperand<uint32_t>(state, {1});
    // Return the proper register operand.
    return GetRegisterDestinationOp<RV32Register>(
      state_, absl::StrCat(RiscVState::kXRegPrefix, num), latency,
      xreg_alias_[num]);
    )
  }
}

এখন InitializeSourceOperandGetters() পদ্ধতিতে যান, যেখানে প্যাটার্ন অনেকটা একই, কিন্তু বিবরণ সামান্য ভিন্ন।

প্রথমে SourceOpEnum দেখে নেওয়া যাক যা প্রথম টিউটোরিয়ালে riscv32i.isa থেকে তৈরি হয়েছিল:

  enum class SourceOpEnum {
    kNone = 0,
    kBimm12 = 1,
    kCsr = 2,
    kImm12 = 3,
    kJimm20 = 4,
    kRs1 = 5,
    kRs2 = 6,
    kSimm12 = 7,
    kUimm20 = 8,
    kUimm5 = 9,
    kPastMaxValue = 10,
  };

সদস্যদের পরীক্ষা করে, kNone ছাড়াও, তারা দুটি গ্রুপে পড়ে। একটি হল তাৎক্ষণিক অপারেন্ড: kBimm12 , kImm12 , kJimm20 , kSimm12 , kUimm20 , এবং kUimm5 । অন্যগুলো হল রেজিস্টার অপারেন্ড: kCsr , kRs1 , এবং kRs2

kNone অপারেন্ডটি গন্তব্য অপারেন্ডের মতোই পরিচালনা করা হয় - একটি nullptr ফেরত দিন।

void RiscV32IEncoding::InitializeSourceOperandGetters() {
  // Source operand getters.
  source_op_getters_[static_cast<int>(SourceOpEnum::kNone)] = [] () {
    return nullptr;
  };

এর পরে, রেজিস্টার অপারেন্ড নিয়ে কাজ করা যাক। আমরা যেভাবে সংশ্লিষ্ট গন্তব্য অপারেন্ডগুলি পরিচালনা করেছি তার মতোই আমরা kCsr পরিচালনা করব - রেজিস্টার নাম হিসাবে "CSR" ব্যবহার করে সাহায্যকারী ফাংশনটিকে কল করুন।

  // Register operands.
  source_op_getters_[static_cast<int>(SourceOpEnum::kCsr)] = [this]() {
    return GetRegisterSourceOp<RV32Register>(state_, "CSR");
  };

অপারেন্ড kRs1 এবং kRs2 kRd এর সমানভাবে পরিচালনা করা হয়, যদিও আমরা x0 (বা zero ) আপডেট করতে চাই না, আমরা নিশ্চিত করতে চাই যে আমরা সবসময় সেই অপারেন্ড থেকে 0 পড়ি। এর জন্য আমরা generic::IntLiteralOperand<> ক্লাসটি literal_operand.h এ সংজ্ঞায়িত ব্যবহার করব। এই অপারেন্ডটি একটি আক্ষরিক মান সংরক্ষণ করতে ব্যবহৃত হয় (একটি সিমুলেটেড তাত্ক্ষণিক মানের বিপরীতে)। অন্যথায় প্যাটার্নটি একই: প্রথমে নির্দেশ শব্দ থেকে rs1/rs2 মানটি বের করুন, যদি এটি শূন্য হয় একটি 0 টেমপ্লেট প্যারামিটার সহ আক্ষরিক অপারেন্ডটি ফেরত দিন, অন্যথায় সহায়ক ফাংশন ব্যবহার করে একটি নিয়মিত রেজিস্টার সোর্স অপারেন্ড ফেরত দিন, abi alias ব্যবহার করে অপারেন্ড নাম।

  source_op_getters_[static_cast<int>(SourceOpEnum::kRs1)] =
      [this]() -> SourceOperandInterface * {
    int num = inst32_format::ExtractRs1(inst_word_);
    if (num == 0) return new IntLiteralOperand<0>({1}, xreg_alias_[0]);
    return GetRegisterSourceOp<RV32Register>(
        state_, absl::StrCat(RiscVState::kXregPrefix, num), xreg_alias_[num]);
  };
  source_op_getters_[static_cast<int>(SourceOpEnum::kRs2)] =
      [this]() -> SourceOperandInterface * {
    int num = inst32_format::ExtractRs2(inst_word_);
    if (num == 0) return new IntLiteralOperand<0>({1}, xreg_alias_[0]);
    return GetRegisterSourceOp<RV32Register>(
        state_, absl::StrCat(RiscVState::kXregPrefix, num), xreg_alias_[num]);
  };

অবশেষে আমরা বিভিন্ন তাৎক্ষণিক অপারেন্ড পরিচালনা করি। তাত্ক্ষণিক মানগুলি ক্লাস generic::ImmediateOperand<> immediate_operand.h তে সংজ্ঞায়িত। তাৎক্ষণিক অপারেন্ডের জন্য বিভিন্ন গেটারের মধ্যে পার্থক্য হল কোন এক্সট্র্যাক্টর ফাংশনটি ব্যবহার করা হয় এবং বিটফিল্ড অনুসারে স্টোরেজ টাইপ স্বাক্ষরিত বা স্বাক্ষরিত কিনা।

  // Immediates.
  source_op_getters_[static_cast<int>(SourceOpEnum::kBimm12)] = [this]() {
    return new ImmediateOperand<int32_t>(
        inst32_format::ExtractBImm(inst_word_));
  };
  source_op_getters_[static_cast<int>(SourceOpEnum::kImm12)] = [this]() {
    return new ImmediateOperand<int32_t>(
        inst32_format::ExtractImm12(inst_word_));
  };
  source_op_getters_[static_cast<int>(SourceOpEnum::kUimm5)] = [this]() {
    return new ImmediateOperand<uint32_t>(
        inst32_format::ExtractUimm5(inst_word_));
  };
  source_op_getters_[static_cast<int>(SourceOpEnum::kJimm20)] = [this]() {
    return new ImmediateOperand<int32_t>(
        inst32_format::ExtractJImm(inst_word_));
  };
  source_op_getters_[static_cast<int>(SourceOpEnum::kSimm12)] = [this]() {
    return new ImmediateOperand<int32_t>(
        inst32_format::ExtractSImm(inst_word_));
  };
  source_op_getters_[static_cast<int>(SourceOpEnum::kUimm20)] = [this]() {
    return new ImmediateOperand<uint32_t>(
        inst32_format::ExtractUimm32(inst_word_));
  };
}

আপনার যদি সাহায্যের প্রয়োজন হয় (অথবা আপনার কাজ পরীক্ষা করতে চান), সম্পূর্ণ উত্তর এখানে

এটি এই টিউটোরিয়ালটি শেষ করে। আমরা এটা দরকারী হয়েছে আশা করি.