इस ट्यूटोरियल का मकसद ये है:
- बाइनरी फ़ॉर्मैट की जानकारी वाली फ़ाइल के स्ट्रक्चर और सिंटैक्स को जानें.
- जानें कि बाइनरी फ़ॉर्मैट की जानकारी, आईएसए फ़ॉर्मैट की जानकारी से कैसे मेल खाती है.
- निर्देशों के RiscV RV32I सबसेट के लिए बाइनरी ब्यौरा लिखें.
खास जानकारी
RiscV बाइनरी निर्देश एन्कोडिंग
बाइनरी निर्देश एन्कोडिंग, निर्देशों को कोड में बदलने का मानक तरीका है पर लागू होता है. आम तौर पर, इन्हें एक्ज़ीक्यूटेबल फ़ाइल में सेव किया जाता है, का इस्तेमाल आम तौर पर ईएलएफ़ फ़ॉर्मैट में करते हैं. निर्देश या तो तय चौड़ाई वाले या अलग-अलग हो सकते हैं चौड़ाई.
आम तौर पर, निर्देश हर फ़ॉर्मैट के साथ कोड में बदलने के फ़ॉर्मैट के छोटे सेट का इस्तेमाल करते हैं कोड में बदले गए निर्देशों के हिसाब से कस्टमाइज़ किया गया हो. उदाहरण के लिए, निर्देशों में ऐसे किसी एक फ़ॉर्मैट का इस्तेमाल किया जा सकता है जो उपलब्ध ऑपकोड की संख्या को बढ़ाता है, हालांकि, तुरंत निर्देश पाने के लिए एक अन्य सुविधा का इस्तेमाल किया जाता है. कोड में बदले जा सकने वाले एडिट का साइज़ बढ़ाने के लिए उपलब्ध ऑपकोड. Branch और जंप के निर्देश करीब-करीब ऐसे फ़ॉर्मैट का इस्तेमाल करते हैं जो बड़े ऑफ़सेट वाली शाखाओं को बढ़ावा देने के लिए तुरंत किया जा सकता है.
उन निर्देशों के फ़ॉर्मैट जिनका इस्तेमाल हम अपने RiscV में डिकोड करने के लिए निर्देशों के मुताबिक करते हैं सिम्युलेटर के बारे में यहां बताया गया है:
आर-टाइप फ़ॉर्मैट, जिसका इस्तेमाल रजिस्टर-रजिस्टर करने के निर्देशों के लिए किया जाता है:
31..25 | 24..20 | 19..15 | 14..12 | 11..7 | 6..0 |
---|---|---|---|---|---|
7 | 5 | 5 | 3 | 5 | 7 |
func7 | rs2 | rs1 | func3 | तीसरा | ऑपकोड |
I-Type फ़ॉर्मैट, इसका इस्तेमाल तुरंत रजिस्टर करने के निर्देशों, लोड करने के निर्देशों, और
jalr
निर्देश, 12 बिट तुरंत.
31..20 | 19..15 | 14..12 | 11..7 | 6..0 |
---|---|---|---|---|
12 | 5 | 3 | 5 | 7 |
imm12 | rs1 | func3 | तीसरा | ऑपकोड |
खास I-Type फ़ॉर्मैट, जिसका इस्तेमाल तुरंत निर्देशों के साथ शिफ़्ट के लिए किया जाता है, 5 बिट तुरंत:
31..25 | 24..20 | 19..15 | 14..12 | 11..7 | 6..0 |
---|---|---|---|---|---|
7 | 5 | 5 | 3 | 5 | 7 |
func7 | uimm5 | rs1 | func3 | तीसरा | ऑपकोड |
U-Type फ़ॉर्मैट, जिसका इस्तेमाल तुरंत निर्देशों के लिए किया जाता है (lui
, auipc
), 20 बिट
तुरंत:
31..12 | 11..7 | 6..0 |
---|---|---|
20 | 5 | 7 |
uimm20 | तीसरा | ऑपकोड |
बी-टाइप फ़ॉर्मैट, जिसका इस्तेमाल कंडिशनल ब्रांच के लिए किया जाता है, जो 12-बिट तुरंत पहले होता है.
31 | 30..25 | 24..20 | 19..15 | 14..12 | 11..8 | 7 | 6..0 |
---|---|---|---|---|---|---|---|
1 | 6 | 5 | 5 | 3 | 4 | 1 | 7 |
इम्यूम | इम्यूम | rs2 | rs1 | func3 | इम्यूम | इम्यूम | ऑपकोड |
J-Type फ़ॉर्मैट, jal
निर्देश के लिए इस्तेमाल किया जाता है. यह 20 बिट फटाफट का होता है.
31 | 30..21 | 20 | 19..12 | 11..7 | 6..0 |
---|---|---|---|---|---|
1 | 10 | 1 | 8 | 5 | 7 |
इम्यूम | इम्यूम | इम्यूम | इम्यूम | तीसरा | ऑपकोड |
S-Type फ़ॉर्मैट, स्टोर के निर्देशों के लिए इस्तेमाल किया जाता है, 12 बिट तुरंत.
31..25 | 24..20 | 19..15 | 14..12 | 11..7 | 6..0 |
---|---|---|---|---|---|
7 | 5 | 5 | 3 | 5 | 7 |
इम्यूम | rs2 | rs1 | func3 | इम्यूम | ऑपकोड |
जैसा कि इन फ़ॉर्मैट में देखा जा सकता है, ये सभी निर्देश 32 बिट लंबे हैं, और हर फ़ॉर्मैट में कम 7 बिट, ऑपरेटर फ़ील्ड है. यह भी ध्यान रखें कि कई फ़ॉर्मैट के साइज़ तुरंत एक जैसे होते हैं, इसलिए उनके बिट इनसे लिए जाते हैं अलग-अलग हिस्सों में लागू किया जाता है. जैसा कि हमें दिखेगा, बाइनरी डिकोडर स्पेसिफ़िकेशन वाले फ़ॉर्मैट की मदद से, इसे समझा जा सकता है.
बाइनरी एन्कोडिंग की जानकारी
निर्देश की बाइनरी एन्कोडिंग को बाइनरी फ़ॉर्मैट में दिखाया जाता है
(.bin_fmt
) जानकारी फ़ाइल. यह
आईएसए में निर्देश शामिल करें, ताकि बाइनरी फ़ॉर्मैट के निर्देश डिकोडर को
जनरेट किया गया. जनरेट किया गया डिकोडर, ऑपकोड का पता लगाता है,
आईएसए के लिए ज़रूरी जानकारी देने के लिए, ऑपरेंड और तुरंत दिखने वाले फ़ील्ड
पिछले ट्यूटोरियल में बताया गया एन्कोडिंग-एग्नोस्टिक डिकोडर.
इस ट्यूटोरियल में हम किसी सबसेट के लिए, बाइनरी एन्कोडिंग की जानकारी वाली फ़ाइल लिखेंगे में इस्तेमाल किए गए निर्देशों को सिम्युलेट करने के लिए ज़रूरी है छोटा "नमस्ते वर्ल्ड" कार्यक्रम. RiscV आईएसए के बारे में ज़्यादा जानकारी के लिए देखें जोखिम-V की जानकारी{.external}.
सबसे पहले फ़ाइल खोलें:
riscv_bin_decoder/riscv32i.bin_fmt
.
फ़ाइल का कॉन्टेंट कई सेक्शन में बांटा गया है.
सबसे पहले, यह decoder
की परिभाषा है.
decoder RiscV32I {
// The namespace in which code will be generated.
namespace mpact::sim::codelab;
// The name (including any namespace qualifiers) of the opcode enum type.
opcode_enum = "OpcodeEnum";
// Include files specific to this decoder.
includes {
#include "riscv_isa_decoder/solution/riscv32i_decoder.h"
}
// Instruction groups for which to generate decode functions.
RiscVInst32;
};
हमारी डिकोडर की परिभाषा, हमारे डिकोडर RiscV32I
के नाम के साथ-साथ, यह भी बताती है कि
Google की मदद करता है. पहला विकल्प namespace
है, जो इसकी जानकारी देता है
वह नेमस्पेस जिसमें जनरेट किया गया कोड रखा जाएगा. दूसरा,
opcode_enum
, जो ऑपकोड इन्यूमरेशन टाइप जनरेट होने का नाम देता है
जनरेट किए गए कोड में आईएसए डिकोडर का इस्तेमाल किया जाना चाहिए. तीसरा,
includes {}
तय करता है कि फ़ाइल में, वे फ़ाइलें शामिल हैं जो इसके लिए जनरेट किए गए कोड के लिए ज़रूरी हैं
यह डिकोडर.
हमारे मामले में, यह वह फ़ाइल है जिसे ISA डिकोडर ने
पिछला ट्यूटोरियल.
अन्य शामिल की गई फ़ाइलों को वैश्विक दायरे वाले includes {}
में तय किया जा सकता है
परिभाषा शामिल नहीं है. यह तब उपयोगी होता है, जब एक से ज़्यादा डिकोडर तय किए गए हों और उन सभी को
का इस्तेमाल किया जा सकता है. चौथी सूची, निर्देश के नामों की है
जो ऐसे निर्देश बनाते हैं जिनके लिए डिकोडर जनरेट किया गया है. हमारे
केस सिर्फ़ एक है: RiscVInst32
.
आगे दी गई फ़ॉर्मैट की तीन परिभाषाएं हैं. ये अलग-अलग निर्देश दिखाते हैं पहले से तय निर्देशों में इस्तेमाल किए जाने वाले 32-बिट निर्देश शब्द के लिए फ़ॉर्मैट फ़ाइल में मौजूद है.
// The generic RiscV 32 bit instruction format.
format Inst32Format[32] {
fields:
unsigned bits[25];
unsigned opcode[7];
};
// RiscV 32 bit instruction format used by a number of instructions
// needing a 12 bit immediate, including CSR instructions.
format IType[32] : Inst32Format {
fields:
signed imm12[12];
unsigned rs1[5];
unsigned func3[3];
unsigned rd[5];
unsigned opcode[7];
};
// RiscV instruction format used by fence instructions.
format Fence[32] : Inst32Format {
fields:
unsigned fm[4];
unsigned pred[4];
unsigned succ[4];
unsigned rs1[5];
unsigned func3[3];
unsigned rd[5];
unsigned opcode[7];
};
पहला यूआरएल, Inst32Format
नाम के 32 बिट के चौड़े निर्देश फ़ॉर्मैट के बारे में बताता है, जिसमें
दो फ़ील्ड: bits
(25 बिट चौड़ी) और opcode
(7 बिट चौड़ी). हर फ़ील्ड
unsigned
का मतलब है कि वैल्यू को निकालने पर, उसकी लंबाई शून्य हो जाएगी
और उसे C++ पूर्णांक टाइप में रखा जाना चाहिए. बिटफ़ील्ड की चौड़ाई का योग ज़रूरी है
फ़ॉर्मैट की चौड़ाई के बराबर हो. अगर कोई गड़बड़ी होती है, तो यह टूल एक गड़बड़ी जनरेट करेगा
असहमति. इस फ़ॉर्मैट का इस्तेमाल किसी दूसरे फ़ॉर्मैट से नहीं किया गया है, इसलिए यह
को टॉप लेवल फ़ॉर्मैट माना जाता है.
दूसरा निर्देश IType
नाम के 32 बिट वाले निर्देश फ़ॉर्मैट के बारे में बताता है, जो कि
Inst32Format
से लिया गया है. इस वजह से, ये दोनों फ़ॉर्मैट मिलते-जुलते हैं. फ़ॉर्मैट में पांच
फ़ील्ड: imm12
, rs1
, func3
, rd
, और opcode
. imm12
फ़ील्ड यह है
signed
का मतलब है कि उस वैल्यू को तब साइन किया जाएगा, जब उस वैल्यू को
एक्सट्रैक्ट करके C++ पूर्णांक टाइप में रखा जाना चाहिए. ध्यान दें कि IType.opcode
दोनों में
वही साइन किया हुआ/साइन नहीं किया गया एट्रिब्यूट और उसी निर्देश शब्द के बिट का हवाला देता है
Inst32Format.opcode
के तौर पर.
तीसरा फ़ॉर्मैट पसंद के मुताबिक बनाया गया फ़ॉर्मैट है, जिसका इस्तेमाल सिर्फ़ fence
करता है
निर्देश दिया गया है, जो एक ऐसा निर्देश है जिसे पहले ही बताया जा चुका है और हमारे पास
चिंता न करें.
खास जानकारी: फ़ील्ड के नामों का इस्तेमाल अलग-अलग मिलते-जुलते फ़ॉर्मैट में तब तक करें, जब तक कि वे वे एक जैसे बिट दिखाते हैं और उनमें साइन/अनसाइन किया गया एट्रिब्यूट एक जैसा होता है.
riscv32i.bin_fmt
में फ़ॉर्मैट की परिभाषाओं के बाद, एक निर्देश ग्रुप आता है
परिभाषा शामिल नहीं है. निर्देश ग्रुप में सभी निर्देश एक जैसे होने चाहिए
और ऐसे प्रारूप का उपयोग करें जो उसी
में दी गई जानकारी शामिल होती है. जब किसी ISA के पास अलग-अलग
लंबाई के लिए, हर लंबाई के लिए एक अलग निर्देश समूह का इस्तेमाल किया जाता है. इसके अलावा,
अगर टारगेट आईएसए का डीकोडिंग, एक्ज़ीक्यूशन मोड पर निर्भर करता है, जैसे कि आर्म बनाम थंब
हर मोड के लिए, एक अलग निर्देश ग्रुप बनाना होगा. कॉन्टेंट बनाने
bin_fmt
पार्सर, हर निर्देश ग्रुप के लिए बाइनरी डिकोडर जनरेट करता है.
instruction group RiscV32I[32] "OpcodeEnum" : Inst32Format {
fence : Fence : func3 == 0b000, opcode == 0b000'1111;
csrs : IType : func3 == 0b010, rs1 != 0, opcode == 0b111'0011;
csrw_nr : IType : func3 == 0b001, rd == 0, opcode == 0b111'0011;
csrs_nw : IType : func3 == 0b010, rs1 == 0, opcode == 0b111'0011;
};
निर्देश समूह एक नाम RiscV32I
, एक चौड़ाई [32]
,
"OpcodeEnum"
का इस्तेमाल करने के लिए, opcode की गिनती का टाइप और एक बुनियादी निर्देश
फ़ॉर्मैट. ऑपकोड की गिनती का टाइप वही होना चाहिए जो
आईएसए के ट्यूटोरियल में बताए गए इंडिपेंडेंट निर्देश डिकोडर का फ़ॉर्मैट
डिकोडर.
कोड में बदलने के हर निर्देशों के ब्यौरे के तीन हिस्से होते हैं:
- ऑपकोड का नाम, जो निर्देश में इस्तेमाल किए गए तरीके से मेल खाना चाहिए डिकोडर की जानकारी, ताकि दोनों एक साथ काम कर सकें.
- ऑपकोड के लिए इस्तेमाल के लिए निर्देश का फ़ॉर्मैट. यह ऐसे फ़ॉर्मैट में इसका इस्तेमाल आखिरी हिस्से में बिटफ़ील्ड के रेफ़रंस के लिए किया जाता है.
- बिट फ़ील्ड कंस्ट्रेंट की कॉमा-सेपरेटेड लिस्ट,
==
,!=
,<
,<=
,>
, और>=
ये सभी सही होने चाहिए, ताकि ऑपरेटर निर्देश शब्द का इस्तेमाल करें.
.bin_fmt
पार्सर इस सारी जानकारी का इस्तेमाल एक डिकोडर बनाने के लिए करता है, जो:
- हर बिट के लिए ज़रूरत के हिसाब से एक्सट्रैक्शन फ़ंक्शन (साइन इन/साइन नहीं किया गया) देता है
फ़ील्ड को खाली छोड़ दें. डेटा इकट्ठा करने वाले फ़ंक्शन को नेमस्पेस में रखा जाता है
यह नाम फ़ॉर्मैट के नाम में मौजूद स्नेक-केस वाले वर्शन के हिसाब से दिया गया है. उदाहरण के लिए,
IType
फ़ॉर्मैट के लिए एक्सट्रैक्टर फ़ंक्शन को नेमस्पेसi_type
में रखा गया है. डेटा इकट्ठा करने वाले हर फ़ंक्शन कोinline
एलान किया जाता है, इसलिए यह सबसे छोटीuint_t
वैल्यू लेता है ऐसा टाइप जो फ़ॉर्मैट की चौड़ाई में बदलाव करता है और सबसे छोटाint_t
दिखाता है (हस्ताक्षर किए गए के लिए),uint_t
(बिना हस्ताक्षर वाला) टाइप, जिसमें एक्सट्रैक्ट किया गया फ़ील्ड होता है चौड़ाई. उदाहरण:
inline uint8_t ExtractOpcode(uint32_t value) {
return value & 0x7f;
}
- हर निर्देश ग्रुप के लिए एक डिकोड फ़ंक्शन. यह फ़ंक्शन का टाइप
OpcodeEnum
, और सबसे छोटाuint_t
टाइप लेता है, जो में बदलाव कर सकते हैं.
शुरुआती बिल्ड करें
डायरेक्ट्री को riscv_bin_decoder
में बदलें और इसका इस्तेमाल करके प्रोजेक्ट बनाएं
निम्न आदेश:
$ cd riscv_bin_decoder
$ bazel build :all
अब अपनी डायरेक्ट्री को वापस डेटा स्टोर करने की जगह के रूट में बदलें. इसके बाद, आइए एक नज़र डालते हैं
जनरेट किए गए सोर्स पर दिखते हैं. इसके लिए, डायरेक्ट्री को इसमें बदलें
bazel-out/k8-fastbuild/bin/riscv_bin_decoder
(यह मानते हुए कि आप x86 पर हैं
होस्ट - अन्य होस्ट के लिए, k8-फ़ास्टबिल्ड कोई दूसरी स्ट्रिंग होगी).
$ cd ..
$ cd bazel-out/k8-fastbuild/bin/riscv_bin_decoder
riscv32i_bin_decoder.h
riscv32i_bin_decoder.cc
जनरेट की गई हेडर फ़ाइल (.h)
riscv32i_bin_decoder.h
खोलें. फ़ाइल के पहले भाग में मानक है
बॉयलरप्लेट गार्ड, इनमें फ़ाइलें और नेमस्पेस की जानकारी शामिल होती है. उसके बाद
internal
नेमस्पेस में एक टेंप्लेट वाला हेल्पर फ़ंक्शन मौजूद है. यह फ़ंक्शन
का इस्तेमाल बहुत लंबे फ़ॉर्मैट से बिट फ़ील्ड निकालने के लिए किया जाता है, ताकि वे 64-बिट C++ में फ़िट हो सकें
पूर्णांक.
#ifndef RISCV32I_BIN_DECODER_H
#define RISCV32I_BIN_DECODER_H
#include <iostream>
#include <cstdint>
#include "third_party/absl/functional/any_invocable.h"
#include "learning/brain/research/mpact/sim/codelab/riscv_isa_decoder/solution/riscv32i_decoder.h"
namespace mpact {
namespace sim {
namespace codelab {
namespace internal {
template <typename T>
static inline T ExtractBits(const uint8_t *data, int data_size,
int bit_index, int width) {
if (width == 0) return 0;
int byte_pos = bit_index >> 3;
int end_byte = (bit_index + width - 1) >> 3;
int start_bit = bit_index & 0x7;
// If it is only from one byte, extract and return.
if (byte_pos == end_byte) {
uint8_t mask = 0xff >> start_bit;
return (mask & data[byte_pos]) >> (8 - start_bit - width);
}
// Extract from the first byte.
T val = 0;
val = data[byte_pos++] & 0xff >> start_bit;
int remainder = width - (8 - start_bit);
while (remainder >= 8) {
val = (val << 8) | data[byte_pos++];
remainder -= 8;
}
// Extract any remaining bits.
if (remainder > 0) {
val <<= remainder;
int shift = 8 - remainder;
uint8_t mask = 0b1111'1111 << shift;
val |= (data[byte_pos] & mask) >> shift;
}
return val;
}
} // namespace internal
शुरुआती सेक्शन के बाद तीन नेमस्पेस का एक सेट है, हर नेमस्पेस के लिए एक
riscv32i.bin_fmt
फ़ाइल में format
एलानों की जानकारी:
namespace fence {
...
} // namespace fence
namespace i_type {
...
} // namespace i_type
namespace inst32_format {
...
} // namespace inst32_format
इनमें से हर नेमस्पेस में, inline
बिटफ़ील्ड एक्सट्रैक्शन फ़ंक्शन
हर बिट फ़ील्ड के लिए तय किया जाता है. इसके अलावा, बेस फ़ॉर्मैट
डिसेंडेंट फ़ॉर्मैट से, एक्सट्रैक्शन फ़ंक्शन को डुप्लीकेट करता है,
1) फ़ील्ड के नाम सिर्फ़ एक फ़ील्ड के नाम में आते हैं या 2) जिसके लिए
फ़ील्ड नाम एक ही प्रकार फ़ील्ड को दिखाते हैं (हस्ताक्षरित/अहस्ताक्षरित और बिट स्थिति)
वे किस फ़ॉर्मैट में होने चाहिए. यह ऐसे बिट फ़ील्ड को चालू करता है जो इसके बारे में जानकारी देते हैं
बिट, जिन्हें टॉप लेवल फ़ॉर्मैट के नेमस्पेस में मौजूद फ़ंक्शन का इस्तेमाल करके निकाला जाता है.
i_type
नेमस्पेस में दिए गए फ़ंक्शन नीचे दिखाए गए हैं:
namespace i_type {
inline uint8_t ExtractFunc3(uint32_t value) {
return (value >> 12) & 0x7;
}
inline int16_t ExtractImm12(uint32_t value) {
int16_t result = ( (value >> 20) & 0xfff) << 4;
result = result >> 4;
return result;
}
inline uint8_t ExtractOpcode(uint32_t value) {
return value & 0x7f;
}
inline uint8_t ExtractRd(uint32_t value) {
return (value >> 7) & 0x1f;
}
inline uint8_t ExtractRs1(uint32_t value) {
return (value >> 15) & 0x1f;
}
} // namespace i_type
आखिर में, निर्देश के लिए डिकोडर फ़ंक्शन का एलान
RiscVInst32
ग्रुप का एलान हो गया है. यह 32 बिट का
निर्देश शब्द और OpcodeEnum
एन्यूमरेशन क्लास सदस्य लौटाता है
का मिलान होता है या अगर कोई मिलान नहीं है तो OpcodeEnum::kNone
.
OpcodeEnum DecodeRiscVInst32(uint32_t inst_word);
जनरेट की गई स्रोत फ़ाइल (.cc)
अब riscv32i_bin_decoder.cc
खोलें. फ़ाइल के पहले भाग में यह शामिल है
#include
और नेमस्पेस की जानकारी के बाद, डिकोडर फ़ंक्शन
एलान:
#include "riscv32i_bin_decoder.h"
namespace mpact {
namespace sim {
namespace codelab {
OpcodeEnum DecodeRiscVInst32None(uint32_t);
OpcodeEnum DecodeRiscVInst32_0(uint32_t inst_word);
OpcodeEnum DecodeRiscVInst32_0_3(uint32_t inst_word);
OpcodeEnum DecodeRiscVInst32_0_3c(uint32_t inst_word);
OpcodeEnum DecodeRiscVInst32_0_5c(uint32_t inst_word);
DecodeRiscVInst32None
का इस्तेमाल खाली डिकोड करने वाली कार्रवाइयों के लिए किया जाता है, जैसे कि
जो OpcodeEnum::kNone
लौटाते हैं. अन्य तीन फ़ंक्शन बनाते हैं,
जनरेट किया गया डिकोडर. कुल डिकोडर, हैरारकी के हिसाब से काम करता है. सेट
निर्देश शब्द में बिट की गणना
या निर्देशों के ग्रुप पर ध्यान देते हैं. बिट के लिए यह ज़रूरी नहीं है कि
आस-पास नहीं होना चाहिए. बिट की संख्या उस लुकअप टेबल का साइज़ तय करती है जो
दूसरे लेवल के डिकोडर फ़ंक्शन से अपने-आप जानकारी भर जाती है. इसे अगले पेज में देखा जाएगा
सेक्शन:
absl::AnyInvocable<OpcodeEnum(uint32_t)> parse_group_RiscVInst32_0[kParseGroupRiscVInst32_0_Size] = {
&DecodeRiscVInst32None, &DecodeRiscVInst32None,
&DecodeRiscVInst32None, &DecodeRiscVInst32_0_3,
&DecodeRiscVInst32None, &DecodeRiscVInst32None,
...
&DecodeRiscVInst32None, &DecodeRiscVInst32None,
&DecodeRiscVInst32None, &DecodeRiscVInst32None,
&DecodeRiscVInst32_0_3c, &DecodeRiscVInst32None,
...
};
आखिर में, डिकोडर फ़ंक्शन परिभाषित किया गया है:
OpcodeEnum DecodeRiscVInst32None(uint32_t) {
return OpcodeEnum::kNone;
}
OpcodeEnum DecodeRiscVInst32_0(uint32_t inst_word) {
if ((inst_word & 0x4003) != 0x3) return OpcodeEnum::kNone;
uint32_t index;
index = (inst_word >> 2) & 0x1f;
index |= (inst_word >> 7) & 0x60;
return parse_group_RiscVInst32_0[index](inst_word);
}
OpcodeEnum DecodeRiscVInst32_0_3(uint32_t inst_word) {
return OpcodeEnum::kFence;
}
OpcodeEnum DecodeRiscVInst32_0_3c(uint32_t inst_word) {
if ((inst_word & 0xf80) != 0x0) return OpcodeEnum::kNone;
return OpcodeEnum::kCsrwNr;
}
OpcodeEnum DecodeRiscVInst32_0_5c(uint32_t inst_word) {
uint32_t rs1_value = (inst_word >> 15) & 0x1f;
if (rs1_value != 0x0)
return OpcodeEnum::kCsrs;
if (rs1_value == 0x0)
return OpcodeEnum::kCsrsNw;
return OpcodeEnum::kNone;
}
OpcodeEnum DecodeRiscVInst32(uint32_t inst_word) {
OpcodeEnum opcode;
opcode = DecodeRiscVInst32_0(inst_word);
return opcode;
}
इस मामले में, जहां सिर्फ़ चार निर्देश परिभाषित किए गए हैं, वहां सिर्फ़ में डिकोड किया जा सकता है. जैसा कि निर्देश जोड़ने के बाद, डिकोडर की संरचना बदल जाएगी और डिकोडर टेबल का क्रम बढ़ सकता है.
रजिस्टर-रजिस्टर करने के लिए ALU के निर्देश जोड़ना
अब riscv32i.bin_fmt
फ़ाइल में कुछ नए निर्देश जोड़े जा सकते हैं. कॉन्टेंट बनाने
निर्देशों का पहला ग्रुप, एएलयू रजिस्टर करने के लिए निर्देश हैं, जैसे कि
add
, and
वगैरह. RiscV32 पर, ये सभी R-टाइप बाइनरी निर्देश का इस्तेमाल करते हैं
फ़ॉर्मैट:
31..25 | 24..20 | 19..15 | 14..12 | 11..7 | 6..0 |
---|---|---|---|---|---|
7 | 5 | 5 | 3 | 5 | 7 |
func7 | rs2 | rs1 | func3 | तीसरा | ऑपकोड |
हमें सबसे पहले फ़ॉर्मैट जोड़ना है. आगे बढ़ें और अपने मन की बात कहें
आपके पसंदीदा एडिटर में riscv32i.bin_fmt
. Inst32Format
के ठीक बाद
RType
नाम का एक फ़ॉर्मैट जोड़ें, जो Inst32Format
से लिया गया है. सभी बिटफ़ील्ड
RType
में unsigned
हैं. नाम, बिट की चौड़ाई, और क्रम का इस्तेमाल करें (बाएं से दाएं)
ऊपर दी गई टेबल से फ़ॉर्मैट तय करें. अगर आपको कोई संकेत चाहिए या आपको
हमारा सुझाव है कि
यहां क्लिक करें.
इसके बाद हमें निर्देश जोड़ने होंगे. निर्देश:
add
- पूर्णांक जोड़ें.and
- बिट के अनुसार और.or
- बिट के अनुसार या.sll
- तार्किक रूप से बाएं शिफ़्ट करें.sltu
- इससे कम, अहस्ताक्षरित.sub
- पूर्णांक को घटाना.xor
- बिट के अनुसार xor.
इनकी एन्कोडिंग हैं:
31..25 | 24..20 | 19..15 | 14..12 | 11..7 | 6..0 | ऑपकोड का नाम |
---|---|---|---|---|---|---|
000 0000 | rs2 | rs1 | 000 | तीसरा | 011 0011 | जोड़ें |
000 0000 | rs2 | rs1 | 111 | तीसरा | 011 0011 | और |
000 0000 | rs2 | rs1 | 110 | तीसरा | 011 0011 | या |
000 0000 | rs2 | rs1 | 001 | तीसरा | 011 0011 | एसएल |
000 0000 | rs2 | rs1 | 011 | तीसरा | 011 0011 | एसएलटीयू |
010 0000 | rs2 | rs1 | 000 | तीसरा | 011 0011 | बदले में खेलने वाला खिलाड़ी |
000 0000 | rs2 | rs1 | 100 | तीसरा | 011 0011 | एक्सओआर |
func7 | func3 | ऑपकोड |
निर्देश की इन परिभाषाओं से पहले
RiscVInst32
निर्देशों का ग्रुप. बाइनरी स्ट्रिंग, लीडिंग के साथ तय की जाती हैं
0b
का प्रीफ़िक्स (हेक्साडेसिमल नंबर के लिए 0x
से मिलता-जुलता). इसे आसान बनाने के लिए
बाइनरी अंकों की लंबी स्ट्रिंग पढ़ें, तो आप एकल कोट '
भी इस रूप में शामिल कर सकते हैं
अंकों को सेपरेटर के तौर पर इस्तेमाल करें.
इनमें से हर एक निर्देश की परिभाषाओं में तीन कंस्ट्रेंट होंगे. ये शर्तें
func7
, func3
, और opcode
. sub
को छोड़कर सभी के लिए, func7
कंस्ट्रेंट
बी:
func7 == 0b000'0000
ज़्यादातर निर्देशों के लिए, func3
कंस्ट्रेंट अलग-अलग होता है. add
और
sub
यह है:
func3 == 0b000
इनमें से हर निर्देश के लिए, opcode
की सीमा एक जैसी है:
opcode == 0b011'0011
सेमीकोलन ;
से हर लाइन को खत्म करना न भूलें.
अब समाधान यह है यहां पढ़ें.
अब पहले की तरह ही अपना प्रोजेक्ट बनाएं और जनरेट किए गए प्रोजेक्ट खोलें
riscv32i_bin_decoder.cc
फ़ाइल. आपको दिखेगा कि अतिरिक्त डिकोडर फ़ंक्शन
नए निर्देशों के हिसाब से जनरेट की गई हैं. ज़्यादातर मामलों में, वे
यह पहले जनरेट किए गए कीवर्ड से मिलता-जुलता है, लेकिन अब
DecodeRiscVInst32_0_c
, जिसका इस्तेमाल add
/sub
डिकोड करने के लिए किया जाता है:
OpcodeEnum DecodeRiscVInst32_0_c(uint32_t inst_word) {
static constexpr OpcodeEnum opcodes[2] = {
OpcodeEnum::kAdd,
OpcodeEnum::kSub,
};
if ((inst_word & 0xbe000000) != 0x0) return OpcodeEnum::kNone;
uint32_t index;
index = (inst_word >> 30) & 0x1;
return opcodes[index];
}
इस फ़ंक्शन में एक स्टैटिक डिकोड टेबल जनरेट हुई है और एक लुकअप वैल्यू है का इस्तेमाल करें. इससे पता चलता है कि निर्देश डिकोडर पदानुक्रम में दूसरी परत होती है, लेकिन जब से ऑपकोड बिना किसी तुलना के सीधे टेबल में देखा जाता है, तो इसकी लाइन के बजाय दूसरे फ़ंक्शन कॉल करने की ज़रूरत नहीं है.
तुरंत ALU निर्देश जोड़ें
निर्देशों का अगला सेट, ALU निर्देश के तौर पर जोड़ा जाएगा. इस सेट में के बजाय तुरंत मान दर्ज करें. इन तीन ग्रुप में निर्देश (तुरंत फ़ील्ड पर आधारित): I-Type तुरंत निर्देश 12 बिट पर तुरंत हस्ताक्षर करके, I-Type विशिष्ट निर्देश तुरंत और U-Type तुरंत, 20-बिट बिना हस्ताक्षर वाली वैल्यू के साथ मिलते हैं. फ़ॉर्मैट नीचे दिखाए गए हैं:
I-Type तात्कालिक प्रारूप:
31..20 | 19..15 | 14..12 | 11..7 | 6..0 |
---|---|---|---|---|
12 | 5 | 3 | 5 | 7 |
imm12 | rs1 | func3 | तीसरा | ऑपकोड |
खास I-Type तात्कालिक प्रारूप:
31..25 | 24..20 | 19..15 | 14..12 | 11..7 | 6..0 |
---|---|---|---|---|---|
7 | 5 | 5 | 3 | 5 | 7 |
func7 | uimm5 | rs1 | func3 | तीसरा | ऑपकोड |
U-Type का मौजूदा फ़ॉर्मैट:
31..12 | 11..7 | 6..0 |
---|---|---|
20 | 5 | 7 |
uimm20 | तीसरा | ऑपकोड |
riscv32i.bin_fmt
में I-Type फ़ॉर्मैट पहले से मौजूद है, इसलिए कोई ज़रूरत नहीं है
वह फ़ॉर्मैट जोड़ना ज़रूरी है.
अगर हम खास तौर पर बनाए गए I-Type फ़ॉर्मैट की तुलना उस R-Type फ़ॉर्मैट से करते हैं जिसमें हमने उसे बताया है
तो हमने देखा कि अंतर सिर्फ़ इतना है कि rs2
फ़ील्ड
का नाम बदलकर uimm5
कर दिया गया है. नया फ़ॉर्मैट जोड़ने के बजाय, हम कॉन्टेंट को बेहतर बनाने के लिए
आर-टाइप फ़ॉर्मैट. हम कोई अन्य फ़ील्ड नहीं जोड़ सकते, क्योंकि इससे फ़ील्ड की चौड़ाई बढ़ जाएगी
फ़ॉर्मैट के हिसाब से बदलाव कर सकते हैं, लेकिन हम एक ओवरले जोड़ सकते हैं. ओवरले,
बिट शामिल होना चाहिए और इसका इस्तेमाल
फ़ॉर्मैट को नाम वाली किसी अलग इकाई में सबमिट करें. इसका खराब असर यह होता है कि जनरेट किया गया कोड
अब ओवरले के लिए एक्सट्रैक्शन फ़ंक्शन भी शामिल होगा,
फ़ील्ड के लिए. इस मामले में, जब rs2
और uimm5
, दोनों पर हस्ताक्षर नहीं किया गया हो
इससे कोई खास फ़र्क़ नहीं पड़ता. सिर्फ़ यह साफ़ तौर पर बताया जाता है कि इस फ़ील्ड का इस्तेमाल किया जा रहा है
तुरंत मदद मिल सकती है. uimm5
नाम के ओवरले को R-Type फ़ॉर्मैट में जोड़ने के लिए, जोड़ें
अंतिम फ़ील्ड के बाद:
overlays:
unsigned uimm5[5] = rs2;
हमें सिर्फ़ U-Type फ़ॉर्मैट ही जोड़ना होगा. इससे पहले कि हम
फ़ॉर्मैट है, तो उस फ़ॉर्मैट का इस्तेमाल करने वाले दो निर्देशों का ध्यान रखें: auipc
और
lui
. ये दोनों, इस्तेमाल करने से पहले 20-बिट की मौजूदा तुरंत वैल्यू को 12 पर शिफ़्ट कर देते हैं
या तो पीसी (auipc
) को इसमें जोड़ें या सीधे रजिस्टर पर लिखें
(lui
). ओवरले का इस्तेमाल करके हम रीयल टाइम,
गणना में थोड़ा बदलाव करके,
डिकोड करना. सबसे पहले, टेबल में दिए गए फ़ील्ड के मुताबिक फ़ॉर्मैट जोड़ें
पढ़ें. इसके बाद, हम इस तरह का ओवरले जोड़ सकते हैं:
overlays:
unsigned uimm32[32] = uimm20, 0b0000'0000'0000;
ओवरले सिंटैक्स की मदद से हम फ़ील्ड को न सिर्फ़ जोड़ सकते हैं, बल्कि करते हैं. इस मामले में हम इसे बाईं ओर शिफ़्ट करने के लिए, इसे 12 शून्यों से जोड़ते हैं 12 साल तक.
हमें ये I-Type निर्देश जोड़ने होंगे:
addi
- तुरंत जोड़ें.andi
- बिट के अनुसार और तुरंत.ori
- बिट के अनुसार या तुरंत.xori
- बिट के हिसाब से xor.
इनकी एन्कोडिंग हैं:
31..20 | 19..15 | 14..12 | 11..7 | 6..0 | opcode_name |
---|---|---|---|---|---|
imm12 | rs1 | 000 | तीसरा | 001 0011 | एडी |
imm12 | rs1 | 111 | तीसरा | 001 0011 | एंडी |
imm12 | rs1 | 110 | तीसरा | 001 0011 | ओरि |
imm12 | rs1 | 100 | तीसरा | 001 0011 | जोरी |
func3 | ऑपकोड |
हमें R-Type (विशेष I-Type) निर्देशों को जोड़ने की आवश्यकता है:
slli
- तुरंत बाईं ओर शिफ़्ट करें.srai
- दाएं अंकगणित को तुरंत शिफ़्ट करें.srli
- तुरंत दाईं ओर तार्किक रूप से शिफ़्ट करें.
इनकी एन्कोडिंग हैं:
31..25 | 24..20 | 19..15 | 14..12 | 11..7 | 6..0 | ऑपकोड का नाम |
---|---|---|---|---|---|---|
000 0000 | uimm5 | rs1 | 001 | तीसरा | 001 0011 | स्ली |
010 0000 | uimm5 | rs1 | 101 | तीसरा | 001 0011 | सराय |
000 0000 | uimm5 | rs1 | 101 | तीसरा | 001 0011 | स्रली |
func7 | func3 | ऑपकोड |
हमें U-Type के लिए ये निर्देश जोड़ने होंगे:
auipc
- पीसी से तुरंत आगे की इमेज जोड़ें.lui
- तुरंत सबसे ऊपर लोड करें.
इनकी एन्कोडिंग हैं:
31..12 | 11..7 | 6..0 | ऑपकोड का नाम |
---|---|---|---|
uimm20 | तीसरा | 001 0111 | auipc |
uimm20 | तीसरा | 011 0111 | लुई |
ऑपकोड |
आगे बढ़ें और बदलाव करें और फिर बनाएं. जनरेट किया गया आउटपुट देखें. बस देख सकते हैं कि पहले की तरह ही, riscv32i.bin_fmt भी शामिल है.
ब्रांच और जंप-ऐंड-लिंक करने के निर्देश जोड़ें
निर्देशों का अगला सेट कंडीशनल ब्रांच है निर्देश, जंप-ऐंड-लिंक निर्देश, और जंप-ऐंड-लिंक रजिस्टर निर्देश दिए गए हों.
हम जिन कंडिशनल ब्रांच को जोड़ रहे हैं वे बी-टाइप एन्कोडिंग का इस्तेमाल करती हैं.
31..25 | 24..20 | 19..15 | 14..12 | 11..7 | 6..0 |
---|---|---|---|---|---|
7 | 5 | 5 | 3 | 5 | 7 |
imm7 | rs2 | rs1 | func3 | imm5 | ऑपकोड |
जबकि B-टाइप एन्कोडिंग, लेआउट में R-टाइप एन्कोडिंग के समान होती है, लेकिन हम
RiscV दस्तावेज़ के साथ अलाइन करने के लिए इसके लिए नए फ़ॉर्मैट टाइप का इस्तेमाल करें.
हालांकि, सही ब्रांच पाने के लिए आपने एक ओवरले भी जोड़ा होगा
आर-टाइप के func7
और rd
फ़ील्ड का इस्तेमाल करते हुए, डिसप्लेसमेंट तुरंत शुरू हो जाता है
एन्कोडिंग
ऊपर बताए गए फ़ील्ड के साथ BType
फ़ॉर्मैट जोड़ना ज़रूरी है, लेकिन नहीं
काफ़ी हैं. जैसा कि आपको दिख रहा है, पहला निर्देश दो निर्देशों के फ़ील्ड में बंटा हुआ है.
इसके अलावा, ब्रांच के निर्देश इसे
दोनों फ़ील्ड को फ़िल्टर कर सकते हैं. इसके बजाय, हर फ़ील्ड को आगे बांटा जाता है और ये
उन्हें किसी अलग क्रम में रखा गया है. अंत में, वह मान बाईं ओर शिफ़्ट हो जाता है
एक 16-बिट संरेखित ऑफ़सेट प्राप्त करने के लिए.
तुरंत बनाने के लिए इस्तेमाल किए गए निर्देश शब्द में बिट के क्रम हैं: 31,
7, 30..25, 11..8. यह नीचे दिए गए सब-फ़ील्ड रेफ़रंस के बारे में बताता है, जहां
इंडेक्स या रेंज, फ़ील्ड में बिट को तय करती है. इन बिट को दाएं से बाएं नंबर दिया जाता है, जैसे कि
imm7[6]
का मतलब imm7
के एमएसबी से है और imm5[0]
का मतलब एलएसबी का है
imm5
.
imm7[6], imm5[0], imm7[5..0], imm5[4..1]
ब्रांच के निर्देशों में इस बिट में बदलाव करने के दो निर्देश हैं
कमियां रह सकती हैं. सबसे पहले, यह सिमैंटिक फ़ंक्शन के लागू करने के तरीके को
में जानकारी दी गई है. दूसरा, यह लंबे समय तक
है. जवाब है BType
फ़ॉर्मैट में ओवरले जोड़ना, जिसमें
पीछे '0' बाईं ओर शिफ़्ट हो जाते हैं.
overlays:
signed b_imm[13] = imm7[6], imm5[0], imm7[5..0], imm5[4..1], 0b0;
ध्यान दें कि ओवरले साइन किया हुआ है. इसलिए, इस ओवरले पर अपने-आप हस्ताक्षर होने की सुविधा चालू हो जाएगी जब इसे निर्देश शब्द से निकाल लिया जाता है.
जंप-एंड-लिंक (तुरंत) निर्देश में, J-Type एन्कोडिंग का इस्तेमाल किया जाता है:
31..12 | 11..7 | 6..0 |
---|---|---|
20 | 5 | 7 |
imm20 | तीसरा | ऑपकोड |
यह जोड़ने में भी आसान फ़ॉर्मैट है. हालांकि, इस फ़ॉर्मैट को तुरंत इस्तेमाल करने के लिए, निर्देश, पढ़ने लायक नहीं हैं. बिट क्रम का इस्तेमाल पूरे तुरंत के हिसाब से ये हैं: 31, 19..12, 20, 30..21 और आखिरी तुरंत है आधा शब्द संरेखण के लिए एक द्वारा बाएं शिफ़्ट किया गया. इसका समाधान यह है कि एक और ओवरले (बाएं शिफ़्ट के लिए खाते में 21 बिट) को फ़ॉर्मैट में बदलें:
overlays:
signed j_imm[21] = imm20[19, 7..0, 8, 18..9], 0b0;
जैसा कि आपको दिख रहा है कि ओवरले के लिए सिंटैक्स, फ़ील्ड को शॉर्टहैंड फ़ॉर्मैट में लिखें. इसके अलावा, अगर किसी फ़ील्ड का नाम इस्तेमाल नहीं किया जाता है, तो बिट संख्याएं निर्देश शब्द को ही दर्शाती हैं, इसलिए ऊपर दिए गए विकल्प इस रूप में लिखा गया:
signed j_imm[21] = [31, 19..12, 20, 30..21], 0b0;
आखिर में, जंप-ऐंड-लिंक (रजिस्टर करें), इस्तेमाल किए गए I-type फ़ॉर्मैट का इस्तेमाल करता है पहले इस्तेमाल कर रहे थे.
I-Type तात्कालिक प्रारूप:
31..20 | 19..15 | 14..12 | 11..7 | 6..0 |
---|---|---|---|---|
12 | 5 | 3 | 5 | 7 |
imm12 | rs1 | func3 | तीसरा | ऑपकोड |
इस बार, फ़ॉर्मैट में कोई बदलाव करने की ज़रूरत नहीं है.
हमें बैंक खाते के लिए ये निर्देश जोड़ने होंगे:
beq
- अगर बराबर है, तो शाखा.bge
- इससे ज़्यादा या इसके बराबर होने पर ब्रांच.bgeu
- ब्रांच, अगर हस्ताक्षर किए गए दस्तावेज़ से ज़्यादा या इसके बराबर है.blt
- इससे कम होने पर ब्रांच.bltu
- ब्रांच, अगर हस्ताक्षर नहीं किए गए से कम है.bne
- अगर बराबर नहीं है, तो शाखा.
उन्हें इस तरह कोड में बदला जाता है:
31..25 | 24..20 | 19..15 | 14..12 | 11..7 | 6..0 | ऑपकोड का नाम |
---|---|---|---|---|---|---|
imm7 | rs2 | rs1 | 000 | imm5 | 110 0011 | Beq |
imm7 | rs2 | rs1 | 101 | imm5 | 110 0011 | बीजीई |
imm7 | rs2 | rs1 | 111 | imm5 | 110 0011 | बीगेयू |
imm7 | rs2 | rs1 | 100 | imm5 | 110 0011 | blt |
imm7 | rs2 | rs1 | 110 | imm5 | 110 0011 | bltu |
imm7 | rs2 | rs1 | 001 | imm5 | 110 0011 | छोटा |
func3 | ऑपकोड |
jal
निर्देश को इस तरह कोड में बदला जाता है:
31..12 | 11..7 | 6..0 | ऑपकोड का नाम |
---|---|---|---|
imm20 | तीसरा | 110 1111 | जल |
ऑपकोड |
jalr
निर्देश को इस तरह कोड में बदला जाता है:
31..20 | 19..15 | 14..12 | 11..7 | 6..0 | opcode_name |
---|---|---|---|---|---|
imm12 | rs1 | 000 | तीसरा | 110 0111 | Jalr |
func3 | ऑपकोड |
आगे बढ़ें और बदलाव करें और फिर बनाएं. जनरेट किया गया आउटपुट देखें. बस देख सकते हैं कि पहले की तरह ही, riscv32i.bin_fmt भी शामिल है.
स्टोर के लिए निर्देश जोड़ें
स्टोर के निर्देश में, एस-टाइप को कोड में बदलने के तरीके का इस्तेमाल किया जाता है, जो B-टाइप के जैसा होता है
शाखा के निर्देशों में इस्तेमाल की गई एन्कोडिंग को छोड़कर,
तुरंत. RiscV के मुताबिक बने रहने के लिए, हम SType
फ़ॉर्मैट को जोड़ते हैं
दस्तावेज़.
31..25 | 24..20 | 19..15 | 14..12 | 11..7 | 6..0 |
---|---|---|---|---|---|
7 | 5 | 5 | 3 | 5 | 7 |
imm7 | rs2 | rs1 | func3 | imm5 | ऑपकोड |
SType
फ़ॉर्मैट के मामले में, इस जवाब को समझने में मदद मिलती है.
दो निकटतम फ़ील्ड का आगे का योग, ताकि ओवरले विशिष्टता
इसका मतलब है:
overlays:
signed s_imm[12] = imm7, imm5;
ध्यान दें कि पूरे फ़ील्ड को जोड़ते समय, किसी बिट रेंज की जानकारी देने वाली सुविधा की ज़रूरत नहीं होती.
स्टोर के निर्देशों को इस तरह कोड में बदला जाता है:
31..25 | 24..20 | 19..15 | 14..12 | 11..7 | 6..0 | ऑपकोड का नाम |
---|---|---|---|---|---|---|
imm7 | rs2 | rs1 | 000 | imm5 | 010 0011 | sb |
imm7 | rs2 | rs1 | 001 | imm5 | 010 0011 | श |
imm7 | rs2 | rs1 | 010 | imm5 | 010 0011 | sw |
func3 | ऑपकोड |
आगे बढ़ें और बदलाव करें और फिर बनाएं. जनरेट किया गया आउटपुट देखें. बस देख सकते हैं कि पहले की तरह ही, riscv32i.bin_fmt भी शामिल है.
लोड करने के निर्देश जोड़ें
डेटा लोड करने के निर्देश, I-Type फ़ॉर्मैट का इस्तेमाल करते हैं. उसमें कोई बदलाव नहीं करना पड़ेगा.
एन्कोडिंग इस तरह हैं:
31..20 | 19..15 | 14..12 | 11..7 | 6..0 | opcode_name |
---|---|---|---|---|---|
imm12 | rs1 | 000 | तीसरा | 000 0011 | lb |
imm12 | rs1 | 100 | तीसरा | 000 0011 | एलबीयू |
imm12 | rs1 | 001 | तीसरा | 000 0011 | एलएच |
imm12 | rs1 | 101 | तीसरा | 000 0011 | एलहू |
imm12 | rs1 | 010 | तीसरा | 000 0011 | lw |
func3 | ऑपकोड |
आगे बढ़ें और बदलाव करें और फिर बनाएं. जनरेट किया गया आउटपुट देखें. बस देख सकते हैं कि पहले की तरह ही, riscv32i.bin_fmt भी शामिल है.
इस ट्यूटोरियल में यह जानकारी दी गई है. हमें उम्मीद है कि यह जानकारी आपके लिए मददगार रही होगी.