সরাসরি পর্বে যান
পর্ব ৩২ · ৬২-এর মধ্যে · মডিউল ৬

Single-cycle datapath — একটি instruction-এর পুরো পথ

The Single-Cycle RISC-V Datapath — Building Blocks, Instruction Paths, Control Lines & Cycle Time

Taught in Bangla, this lesson builds a single-cycle RISC-V datapath piece by piece: state elements and combinational units, instruction fetch, R-type, load/store and branch paths, and the ALUSrc, MemtoReg and PCSrc multiplexers. It traces lw and beq with real wire values, works out critical paths, per-instruction times and the clock period as exam numericals, and adds addi and jal, with a runnable single-cycle simulator.

এই পর্বে যা শিখবেন

  • RISC-V-এর একটি ছোট CPU-র datapath টুকরো টুকরো করে নিজে আঁকতে পারা — fetch, R-type, load/store, branch
  • কোন instruction কোন unit ব্যবহার করে, আর প্রতিটি তারে কোন মান থাকে, তা সংখ্যা ধরে trace করা
  • ALUSrc, MemtoReg, PCSrc mux কেন লাগে, আর একটি control line আটকে গেলে কী ভাঙে
  • Unit-এর delay থেকে প্রতিটি instruction-এর সময়, clock period আর অপচয়ের হিসাব — পরীক্ষার ক্লাসিক numerical
  • addi আর jal যোগ করতে datapath-এ কী বদলাতে হয়

১ · Instruction থেকে hardware — datapath আর control

এতদিন আমরা instruction দেখেছি software-এর চোখে। পর্ব ২৬-এ শিখেছেন lw x5, 8(x10) মানে “x10-এর ঠিকানার ৮ byte পরের word এনে x5-এ রাখা”। পর্ব ২৭-এ দেখেছেন সেই instruction আসলে ৩২টি বিট, 0x00852283. কিন্তু প্রশ্নটা রয়ে গেছে: chip-এর ভেতরে কোন তার দিয়ে কোন মান কোথায় যায়, যাতে ঠিক এই কাজটি হয়? এই পর্বে আমরা প্রথমবার একটি আসল CPU-র ভেতরটা নিজের হাতে বানাব।

একটি CPU-র hardware-কে দুই ভাগে দেখা হয়। প্রথম ভাগ datapath — যেখানে ডেটা চলে আর হিসাব হয়: register, ALU, মেমরি, adder, mux আর তাদের মধ্যের তার। দ্বিতীয় ভাগ control unit — যে instruction-এর opcode দেখে datapath-কে বলে দেয় কোন mux কোন ইনপুট নেবে, কোন register-এ লেখা হবে, মেমরি পড়বে না লিখবে। এই নির্দেশগুলোকে বলে control signal.

একটি রেস্তোরাঁর রান্নাঘর ভাবুন। চুলা, কাটার টেবিল, ফ্রিজ, তন্দুর — এগুলো datapath. ওয়েটার যে অর্ডার-স্লিপ দেন, সেটি instruction. আর হেড শেফ স্লিপ পড়ে চিৎকার করে বলেন “ফ্রিজ থেকে মাংস, তন্দুর চালু, চুলা বন্ধ” — তিনি control unit. একই রান্নাঘরে বিরিয়ানি, কাবাব বা সালাদ হয়; রান্নাঘর বদলায় না, শুধু শেফের নির্দেশ বদলায়। Single-cycle রান্নাঘরের নিয়ম আরও কড়া: প্রতিটি অর্ডারের জন্য ঘণ্টা একবার বাজে, আর সবচেয়ে সময়সাপেক্ষ পদের হিসাবে ঘণ্টার ফাঁক ঠিক করা হয় — সালাদ এক মিনিটে হলেও পরের অর্ডার শুরু হবে পরের ঘণ্টায়।

এই পর্বে আমরা Patterson–Hennessy-এর বই Computer Organization and Design, RISC-V Edition-এর ধাঁচে একটি ছোট CPU বানাব, যা RV32I-এর আটটি instruction চালায়:

শ্রেণিinstructionformat (পর্ব ২৭)কী করে
R-type ALUadd, sub, and, orRrd ← rs1 op rs2
Immediate ALUaddiIrd ← rs1 + imm
LoadlwIrd ← Mem[rs1 + imm]
StoreswSMem[rs1 + imm] ← rs2
BranchbeqBrs1 = rs2 হলে PC ← PC + imm, নইলে PC + 4

ছোট তালিকা, কিন্তু এখানে CPU-র সব মৌলিক কাজ আছে: হিসাব, মেমরি থেকে আনা, মেমরিতে রাখা আর সিদ্ধান্ত নিয়ে লাফ দেওয়া। এই আটটি দিয়েই লুপ, array-র যোগফল বা খোঁজার প্রোগ্রাম লেখা যায়। অন্য instruction যোগ করার উপায় অংশ ৮-এ দেখব।

মূল ধারণা

Single-cycle মানে একটি instruction শুরু থেকে শেষ পর্যন্ত ঠিক একটি clock cycle নেয়। cycle-এর শুরুতে PC একটি ঠিকানা দেয়; cycle চলাকালীন সব combinational unit একটার পর একটা মান হিসাব করে; আর cycle-এর শেষে, clock-এর ধারে, ফলাফল register file, মেমরি আর PC-তে একসাথে লেখা হয়। Iron law (পর্ব ০৩)-এর ভাষায় CPI = ১, কিন্তু cycle time Tc লম্বা।

২ · উপাদানগুলো — state element আর combinational unit

Datapath-এর প্রতিটি উপাদান দুই জাতের একটি। Combinational unit-এর আউটপুট শুধু এই মুহূর্তের ইনপুটের ওপর নির্ভর করে — ALU, adder, mux, ImmGen. এরা কিছু মনে রাখে না। আর state element মান ধরে রাখে, আর শুধু clock-এর ধারে নতুন মান নেয় — PC, register file, data memory. (Instruction memory-কে এই মডেলে শুধু পড়া হয়, তাই সেটি combinational-এর মতো আচরণ করে: ঠিকানা দিলে কিছুক্ষণ পরে instruction আসে।) পর্ব ১৫-১৬-এ (flip-flop, register) যে edge-triggered flip-flop দেখেছেন, state element তারই বড় রূপ।

উপাদানজাতইনপুট → আউটপুটকোন পর্বে বিস্তারিত
PCstate (৩২-বিট register)পরের ঠিকানা → এখনকার ঠিকানা১৬
Instruction memoryশুধু পড়াঠিকানা → ৩২-বিট instruction৪৪-৪৯
Adder (PC + 4)combinationalPC, 4 → PC + 4১১
Register filestaters1, rs2 → RD1, RD2; rd, WD, RegWrite → লেখা১২, ১৬
ALUcombinationalA, B, ALU control → ফল, Zero১৩
Data memorystateঠিকানা, write data, MemRead/MemWrite → read data৪৪-৪৯
ImmGencombinationalinstruction → ৩২-বিট immediate২৭
Muxcombinationalদুই ইনপুট, select → একটি১২

Register file — দুইটি পড়া, একটি লেখা

R-type instruction একবারে দুটি register পড়ে আর একটিতে লেখে, তাই register file-এ দুটি read port আর একটি write port. পর্ব ১২-তে (mux আর decoder) দেখেছেন: প্রতিটি read port আসলে ৩২টি register-এর মধ্যে একটি বাছাইয়ের একটি চওড়া mux — পড়া combinational, ঠিকানা দিলেই কিছুক্ষণ পরে মান। লেখা হয় শুধু clock-এর উঠন্ত ধারে, আর শুধু যখন RegWrite = 1; কোন register-এ লেখা হবে তা ঠিক করে একটি ৫-থেকে-৩২ decoder. আর x0 একটি বিশেষ ঘর: পড়লে সবসময় 0, লেখার চেষ্টা উপেক্ষা হয়।

// উদাহরণ — synthesizable Verilog (পর্ব ১৪-এর ধাঁচে), ৩২ × ৩২-বিট register file
module regfile (
  input  wire        clk, we,           // we = RegWrite
  input  wire [4:0]  ra1, ra2, wa,      // rs1, rs2, rd
  input  wire [31:0] wd,                // write data
  output wire [31:0] rd1, rd2           // ReadData1, ReadData2
);
  reg [31:0] r [0:31];
  assign rd1 = (ra1 == 5'd0) ? 32'd0 : r[ra1];   // combinational read
  assign rd2 = (ra2 == 5'd0) ? 32'd0 : r[ra2];   // x0 always reads 0
  always @(posedge clk)
    if (we && wa != 5'd0) r[wa] <= wd;          // write only at the edge
endmodule

এই নকশার একটি চমৎকার ফল: add x5, x5, x6-এর মতো instruction একই cycle-এ x5 পড়ে আর x5-এ লেখে, তবু গোলমাল হয় না। পুরো cycle ধরে পড়া হয় পুরোনো মান; নতুন মান ঢোকে cycle-এর শেষের ধারে। পরের cycle-এর instruction নতুনটি দেখে। এই নিয়মকে বলে clocking methodology: সব state element একই ধারে লেখে, আর দুই ধারের মাঝে combinational logic-এর মান স্থির হওয়ার সময় পায়।

ALU আর ImmGen

পর্ব ১৩-এর ALU এখানে চারটি কাজ করবে: AND, OR, যোগ, বিয়োগ। কোনটি করবে তা বলে ৪-বিটের ALU control: Patterson–Hennessy-এর কোডে 0000 = AND, 0001 = OR, 0010 = যোগ, 0110 = বিয়োগ। ALU-র একটি বাড়তি আউটপুট Zero — ফল 0 হলে 1. beq ঠিক এটাই ব্যবহার করবে: rs1 − rs2 = 0 মানে দুটি সমান।

ImmGen (immediate generator) instruction-এর বিট থেকে immediate বের করে ৩২ বিটে sign extend করে। কোন বিট কোথা থেকে আসবে তা format-ভেদে আলাদা — I-তে inst[31:20], S-এ inst[31:25] আর inst[11:7], B-তে চার টুকরো। পুরো টেবিল পর্ব ২৭-এ; এখানে মনে রাখুন ImmGen opcode দেখে format বোঝে, আর sign bit সবসময় inst[31].

Branch-এর “Shift left 1” নিয়ে একটি সতর্কতা। Patterson–Hennessy-এর RISC-V ছবিতে branch-এর পথে একটি “Shift left 1” বাক্স আছে। কারণ: B-format-এ লেখা থাকে imm[12:1], আর বইয়ের ImmGen সেই ১২টি বিটকেই sign extend করে দেয় — অর্থাৎ offset/২. তারপর ১ ঘর বাঁয়ে সরিয়ে byte-এর offset পাওয়া যায়। এটি আসলে কোনো gate নয়, শুধু তারগুলো এক ঘর সরিয়ে জোড়া। Harris & Harris-এর বইসহ অনেক নকশায় ImmGen সরাসরি imm[0] = 0 বসিয়ে পুরো byte offset দেয়, তখন বাক্সটিই থাকে না। MIPS-সংস্করণের পুরোনো ছবিতে ছিল “Shift left 2”, কারণ MIPS-এর branch offset গোনা হতো word-এ — AI প্রায়ই এই দুটি গুলিয়ে ফেলে।

Datapath আর control-কে আলাদা করে ভাবার ধারণা পুরোনো। ১৯৪৫ সালে John von Neumann-এর EDVAC-এর খসড়া প্রতিবেদনে গাণিতিক অংশ, নিয়ন্ত্রণ অংশ আর মেমরিকে আলাদা অঙ্গ হিসেবে বর্ণনা করা হয়। ১৯৫১ সালে Maurice Wilkes control অংশকে নিয়মিত করার জন্য microprogramming-এর প্রস্তাব দেন (পর্ব ৩৪)। আর আজকের পাঠ্যবইয়ের “একটি ছবিতে পুরো datapath” রীতি জনপ্রিয় হয় Patterson আর Hennessy-এর বই থেকে — প্রথমে MIPS দিয়ে, ২০১৭ সালের RISC-V সংস্করণ থেকে RISC-V দিয়ে।

৩ · প্রথম দুই টুকরো — instruction fetch আর R-type

Fetch: PC → instruction memory, আর PC + 4

প্রতিটি instruction-এর কাজ শুরু একইভাবে। PC-র মান যায় instruction memory-র ঠিকানায়, আর কিছুক্ষণ পরে বেরিয়ে আসে ৩২-বিটের instruction. একই সময়ে PC-র মান যায় একটি আলাদা adder-এ, যেখানে ৪ যোগ হয় — কারণ RV32I-এর প্রতিটি instruction ৪ byte, আর মেমরি byte-ঠিকানার। Branch না থাকলে পরের clock-এর ধারে PC-তে বসবে PC + 4. এই অংশটিকে বলে instruction fetch.

প্রশ্ন জাগতে পারে, ALU তো যোগ করতে পারে — PC + 4-এর জন্য আলাদা adder কেন? কারণ single-cycle-এ প্রতিটি unit এক cycle-এ শুধু একবার ব্যবহার করা যায়। add-এর cycle-এ ALU ব্যস্ত rs1 + rs2 নিয়ে; একই সময়ে PC + 4-ও দরকার। তাই আলাদা adder. একই কারণে instruction আর ডেটার মেমরিও আলাদা: lw-এর একটি cycle-এ দুবার মেমরি লাগে — একবার instruction আনতে, একবার ডেটা আনতে। এটি Harvard-ধাঁচের নকশা।

R-type: দুই register পড়া, ALU, এক register-এ লেখা

add x7, x5, x6 নিন। Instruction-এর বিট ১৯..১৫ (rs1 = 5) যায় register file-এর প্রথম read port-এ, বিট ২৪..২০ (rs2 = 6) দ্বিতীয়টিতে, বিট ১১..৭ (rd = 7) যায় write-এর ঠিকানায়। RD1 আর RD2 সরাসরি ALU-র দুই ইনপুটে; ALU-র ফল ফিরে আসে register file-এর write data (WD)-তে। RegWrite = 1, তাই cycle-এর শেষের ধারে x7-এ ফল লেখা হয়। কোন কাজ (যোগ, বিয়োগ, AND, OR) — তা ঠিক করে funct3 আর funct7-এর বিট ৩০, ALU control-এর মাধ্যমে।

পর্ব ২৭-এ দেখেছিলেন rs1, rs2, rd যে format-এ আছে, সবখানে একই বিট-জায়গায় থাকে। এখানে তার লাভটা চোখে দেখা যায়: instruction-এর বিটগুলো সরাসরি তার দিয়ে register file-এ যায়, কোনো বাছাই ছাড়াই — control unit opcode বোঝার আগেই register পড়া শুরু হয়ে যায়।

৪ · আরও দুই টুকরো — load/store আর branch

Load আর store: ঠিকানা = rs1 + imm

lw x5, 8(x10)-এ ঠিকানা = x10 + ৮. ঠিকানার যোগটি ALU-ই করে, কিন্তু এবার দ্বিতীয় ইনপুট register নয়, ImmGen-এর আউটপুট। ALU-র ফল যায় data memory-র ঠিকানায়; MemRead = 1, তাই মেমরি সেই word ফেরত দেয়; আর সেই মান যায় register file-এর WD-তে, rd = x5-এ লেখার জন্য।

sw x7, 16(x10)-এ ঠিকানার হিসাব হুবহু এক (rs1 + imm), কিন্তু rs2-এর মান (RD2) যায় data memory-র write data-তে, MemWrite = 1, আর register file-এ কিছু লেখা হয় না (RegWrite = 0). এখানে ImmGen-কে S-format-এর দুই টুকরো জুড়তে হয়। Load-store ছাড়া আর কোনো instruction মেমরি ছোঁয় না — এটাই load-store architecture, আর তাই datapath-এ data memory মাত্র একটি জায়গায় বসে।

Branch: target = PC + imm, সিদ্ধান্ত Zero থেকে

beq x7, x8, 8-এর দুটি কাজ। প্রথমত তুলনা: RD1 আর RD2 ALU-তে যায়, ALU বিয়োগ করে, আর Zero = 1 মানে দুটি সমান। দ্বিতীয়ত লক্ষ্য-ঠিকানা: আরেকটি adder PC আর ImmGen-এর offset (Shift left 1-এর পরে) যোগ করে branch target বানায়। ALU তুলনায় ব্যস্ত, তাই এই যোগের জন্যও আলাদা adder. শেষে একটি সিদ্ধান্ত: Branch control signal = 1 আর Zero = 1 হলে PC-তে যাবে target, নইলে PC + 4.

খেয়াল করুন, offset যোগ হয় এই instruction-এর নিজের PC-র সাথে, PC + 4-এর সাথে নয় — RISC-V-এর নিয়ম এটাই (পর্ব ২৯)। MIPS-এ ছিল উল্টো, PC + 4-এর সাথে; পুরোনো বই থেকে শিখলে এখানে ভুল হয়।

৫ · সব জোড়া লাগানো — তিনটি mux আর পূর্ণ datapath

এবার চারটি টুকরো একটি circuit-এ জুড়তে হবে, যাতে একই hardware আটটি instruction-ই চালায়। যেখানেই একটি ইনপুটে দুই জায়গা থেকে মান আসতে চায়, সেখানে একটি mux বসে। তিনটি এমন জায়গা আছে:

  • ALUSrc mux — ALU-র দ্বিতীয় ইনপুট: R-type আর beq চায় RD2 (0), lw, sw, addi চায় immediate (1).
  • MemtoReg mux — register file-এ কী লেখা হবে: R-type আর addi চায় ALU-র ফল (0), lw চায় মেমরি থেকে আসা মান (1).
  • PCSrc mux — PC-র পরের মান: সাধারণত PC + 4 (0), branch নেওয়া হলে target (1). PCSrc = Branch AND Zero.

নিচের stepper-এ দেখুন datapath কীভাবে একটি একটি উপাদান আর mux যোগ করে বড় হয়:

ধাপ ১fetch — ৩টি উপাদান, ০টি mux
PC ──► Instruction memory ──► instruction
PC ──► [ + 4 ] ──► PC   (পরের clock-এর ধারে)

উপাদান: PC, IMem, adder
চলে: কিছুই না — শুধু instruction আসে
ধাপ ২+ R-type — register file আর ALU
inst[19:15] ──► RF rs1 ──► RD1 ──► ALU A
inst[24:20] ──► RF rs2 ──► RD2 ──► ALU B
ALU result  ──► RF WD   (rd = inst[11:7])
control: RegWrite, ALU control

চলে: add, sub, and, or
ধাপ ৩+ lw/sw — ImmGen, data memory, ২টি mux
ALU B   ◄── ALUSrc mux  (0: RD2 │ 1: imm)
RF WD   ◄── MemtoReg mux (0: ALU │ 1: Mem)
ALU result ──► DMem address
RD2        ──► DMem write data
control: + ALUSrc, MemtoReg, MemRead, MemWrite

চলে: + lw, sw, addi
ধাপ ৪+ beq — target adder আর PCSrc mux
imm ──► [<<1] ──► [ Add ] ◄── PC   = target
PC  ◄── PCSrc mux (0: PC+4 │ 1: target)
PCSrc = Branch AND Zero
control: + Branch, ALUOp = 01 (বিয়োগ)

চলে: + beq
ধাপ ৫+ control unit — পূর্ণ datapath
inst[6:0] ──► Control ──► ALUSrc, MemtoReg,
                         RegWrite, MemRead,
                         MemWrite, Branch, ALUOp
ALUOp + inst[30,14:12] ──► ALU control

মোট: ৩টি mux, ২টি adder, ২টি মেমরি,
১টি register file, ১টি ALU, ১টি ImmGen
  1. শুরু fetch দিয়ে। PC, instruction memory আর একটি adder. প্রতি cycle-এ একটি instruction আসে আর PC চার করে বাড়ে। এখনো কোনো instruction “কাজ” করে না — শুধু পড়া হয়।
  2. R-type যোগ। Register file আর ALU বসল। instruction-এর বিট সরাসরি register-এর ঠিকানা; ALU-র ফল ফিরে যায় register file-এ। কোনো mux লাগেনি, কারণ প্রতিটি ইনপুটের উৎস এখনো একটিই।
  3. Load/store যোগ। ALU-র B ইনপুট এখন দুই জায়গা থেকে চায় — RD2 আর immediate. তাই প্রথম mux, ALUSrc. Register file-এর WD-ও দুই উৎস চায় — ALU আর মেমরি। তাই দ্বিতীয় mux, MemtoReg. addi এই ধাপেই বিনা খরচে চলে আসে।
  4. Branch যোগ। PC-র পরের মানের এখন দুই উৎস — PC + 4 আর target. তাই তৃতীয় mux, PCSrc, যার select আসে Branch আর Zero-এর AND থেকে। target-এর জন্য আরেকটি adder, কারণ ALU তুলনায় ব্যস্ত।
  5. Control unit বসল। opcode (inst[6:0]) দেখে control unit সাতটি সংকেত দেয়; ALU control নেয় ALUOp আর funct-এর বিট। এখন একই hardware আটটি instruction চালায় — শুধু সংকেতের মান বদলে।

এবার পুরো ছবিটি একসাথে। কালো তার ডেটা বহন করে, কমলা ড্যাশ-দাগ control সংকেত। ছবিটি বড়; ফোনে আঙুল দিয়ে zoom করে দেখতে পারেন।

PC Instructionmemory Registers Datamemory ALU Add Add <<1 ImmGen ALU control Control Address Instruction 4 rs1rs2rdWD RD1RD2 [6:0][19:15][24:20] [11:7][31:0][30, 14:12] AddressWrite dataRead data resultZero 01 10 01 PC + 4branch targetimm write-back (WD) RegWriteALUSrcALUOp BranchMemWrite MemReadMemtoRegPCSrc control সংকেত নীল = state element, মেমরি আর combinational unit · বেগুনি = mux · কমলা ড্যাশ = control · ▲ = clock-এর ধারে লেখে
ছবি ৩২.১ — পূর্ণ single-cycle datapath (Patterson–Hennessy RISC-V ধাঁচ)। তিনটি mux: ALUSrc (ALU-র B), MemtoReg (লেখার মান), PCSrc (পরের PC). ▲ চিহ্ন দেওয়া তিনটি state element — PC, register file, data memory — শুধু clock-এর ধারে লেখে।

Control signal-এর এক ঝলক

প্রতিটি instruction শ্রেণির জন্য control unit কোন মান দেয়, তার সংক্ষিপ্ত টেবিল নিচে। এই টেবিল কীভাবে truth table থেকে gate-এ বানানো হয়, আর ALU control-এর পুরো নকশা — সেটি পর্ব ৩৩-এর বিষয়। এখানে শুধু মানগুলো পড়তে শিখুন; X মানে don't care — যেটিই হোক ফল বদলায় না।

instructionALUSrcMemtoRegRegWriteMemReadMemWriteBranchALUOp
R-type00100010
lw11110000
sw1X001000
beq0X000101
addi*10100000

ALUOp = 00 মানে যোগ (ঠিকানার হিসাব), 01 মানে বিয়োগ (beq-এর তুলনা), 10 মানে funct3/funct7 দেখে কাজ বাছাই (R-type). *addi Patterson–Hennessy-এর মূল টেবিলে নেই; এখানে তাকে ALUOp = 00 (যোগ) দেওয়া হলো, 10 নয় — কারণ addi-এর inst[30] immediate-এর একটি বিট; ALUOp = 10 দিলে ALU control সেটিকে funct7-এর বিট ভেবে ঋণাত্মক immediate-এ যোগের বদলে বিয়োগ করে ফেলত। andi, ori, slti যোগ করলে ALU control-কে funct3 দেখতে হবে, তখন আলাদা ALUOp লাগে — পর্ব ৩৩-এ।

৬ · মান ধরে trace — প্রতিটি তারে কী আছে

ছবি মুখস্থ করে লাভ কম; আসল বোঝা আসে একটি instruction-কে সংখ্যা ধরে এক unit থেকে আরেক unit-এ নিয়ে গেলে। ধরুন শুরুতে x10 = 0x1000, মেমরির 0x1008 ঠিকানায় ৪২ আর 0x100C-তে ৮, PC = 0x00. প্রথম instruction lw x5, 8(x10), যার word 0x00852283. নিচে প্রতিটি ধাপে কোন তারে কোন মান, আর সেই মান কখন স্থির হয় (অংশ ৭-এর পাঠ্যবই-সংখ্যায়: I-mem ২০০, register পড়া ৫০, ALU ১০০, D-mem ২০০, register-এ লেখার setup ৫০ ps), দেখুন।

  1. FetchPC → IMem২০০ ps
  2. DecodeControlসংকেত
  3. Reg + ImmRF, ImmGen+৫০ ps
  4. ALUrs1 + imm+১০০ ps
  5. DMemread+২০০ ps
  6. Write-backx5 ← 42+৫০ ps
তারের মান
PC
Instruction
ALUSrc / MemtoReg / RegWrite / MemRead
RD1 (x10)
RD2 (x8, অব্যবহৃত)
ImmGen
ALU result
Read data
WD (MemtoReg = 1)
x5
সময় (ps)
  1. Fetch. PC = 0x00 যায় instruction memory-তে; ২০০ ps পরে বেরোয় 0x00852283. একই সময়ে PC + 4-এর adder 0x04 বানিয়ে রাখে। Control-এর মান এখনো অজানা (?)।
  2. Decode. opcode 0000011 (LOAD) দেখে control unit দেয় ALUSrc = 1, MemtoReg = 1, RegWrite = 1, MemRead = 1, MemWrite = 0, Branch = 0, ALUOp = 00. এই পাঠ্যবই-মডেলে control-এর delay শূন্য ধরা, আর register পড়ার সাথে সমান্তরালে চলে।
  3. Register পড়া আর ImmGen. rs1 = x10 থেকে RD1 = 0x1000. বিট ২৪..২০-এ আছে 01000 = ৮ — lw-এ এটি immediate-এর অংশ, কিন্তু register file জানে না, তাই x8 (= 0) পড়ে ফেলে; ALUSrc = 1 বলে এই মান কেউ ব্যবহার করে না। ImmGen inst[31:20] থেকে দেয় ৮. সময় ২৫০ ps.
  4. ALU. ALUSrc = 1, তাই B = imm = ৮. ALUOp = 00 মানে যোগ: 0x1000 + 8 = 0x1008. এটি ঠিকানা, কোনো register-এর ফল নয়। সময় ৩৫০ ps.
  5. Data memory. ঠিকানা 0x1008, MemRead = 1. ২০০ ps পরে read data = ৪২. পুরো datapath-এ এটিই সবচেয়ে দীর্ঘ পথ — দুটি মেমরি এক cycle-এ পরপর। সময় ৫৫০ ps.
  6. Write-back. MemtoReg = 1, তাই WD = ৪২ (ALU-র 0x1008 নয়)। rd = x5, RegWrite = 1. মানটি register file-এর ইনপুটে setup-এর জন্য আরও ৫০ ps স্থির থাকতে হবে: ৬০০ ps. clock period এর কম হলে x5-এ ভুল মান ঢুকতে পারত।
  7. Clock-এর ধার। ৬০০ ps-এ উঠন্ত ধার: x5 ← ৪২ আর PC ← 0x04 (Branch = 0, তাই PCSrc = 0) — একই মুহূর্তে। নতুন cycle শুরু, পরের instruction lw x6, 12(x10).

R-type-এর পথ এর চেয়ে ছোট: add x7, x5, x6-এ RD1 = ৪২, RD2 = ৮, ALUSrc = 0 বলে B = ৮, ALU ফল ৫০, MemtoReg = 0 বলে WD = ৫০. Data memory চুপচাপ বসে থাকে (MemRead = MemWrite = 0). sw x7, 16(x10)-এ ঠিকানা 0x1010, write data = RD2 = ৫০, MemWrite = 1, আর ধারে মেমরিতে ৫০ লেখা হয়; register file-এ কিছু নয়। কোন instruction কোন unit ব্যবহার করে, এক টেবিলে:

শ্রেণিIMemRF পড়াImmGenALUDMemRF লেখাtarget adder
R-type✓rs1, rs2—op—✓—
addi✓rs1Iযোগ—✓—
lw✓rs1Iযোগপড়া✓—
sw✓rs1, rs2Sযোগলেখা——
beq✓rs1, rs2Bবিয়োগ——✓

“—” মানে unit-টি সেই cycle-এ কোনো কাজে লাগে না, কিন্তু hardware তো আছে, তার মধ্যে বিদ্যুৎও যায়। Single-cycle-এ কোনো unit অন্য instruction-কে ধার দেওয়া যায় না — সবাই একই cycle-এ একটি instruction-এর জন্য বসে থাকে।

beq — নেওয়া আর না-নেওয়া

একই beq x7, x8, 8 word (0x00838463) প্রোগ্রামে দুবার আছে: 0x14-এ, যখন x7 = ৫০ আর x8 = ৩৪, আর 0x1C-তে, যখন মাঝের addi x8, x8, 16 x8-কে ৫০ বানিয়েছে। Branch নেওয়া হবে কি না, তা পুরোপুরি ALU-র Zero আর PCSrc mux-এর ওপর নির্ভর করে:

branch-এর তার
PC
Branch / ALUOp / RegWrite
RD1 (x7) − RD2 (x8)
ALU result
Zero
PC + 4
ImmGen → <<1 → target
PCSrc = Branch AND Zero
পরের PC
  1. Decode. opcode 1100011 (BRANCH): Branch = 1, ALUOp = 01 (বিয়োগ), ALUSrc = 0, RegWrite = 0, MemWrite = 0. একই সময়ে target adder PC + offset হিসাব শুরু করে — তুলনার ফল জানার আগেই, কারণ hardware-এ দুটি একসাথে চলাই সস্তা। ImmGen দেয় imm[12:1] = ৪, Shift left 1-এর পরে ৮, target = 0x14 + 8 = 0x1C.
  2. তুলনা (প্রথমবার). RD1 = x7 = ৫০, RD2 = x8 = ৩৪. ALUSrc = 0, তাই B = RD2. ALU বিয়োগ করে ১৬ — শূন্য নয়।
  3. না-নেওয়া। Zero = 0, তাই PCSrc = 1 AND 0 = 0. PCSrc mux বেছে নেয় PC + 4 = 0x18. হিসাব করা target 0x1C ফেলে দেওয়া হয় — নষ্ট কাজ, কিন্তু সময় বাঁচানোর জন্য ইচ্ছাকৃত।
  4. দ্বিতীয়বার, 0x1C-তে। মাঝে addi x8, x8, 16 চলেছে: x8 = ৩৪ + ১৬ = ৫০. এখন RD1 = RD2 = ৫০, ALU ফল ০.
  5. নেওয়া। Zero = 1, PCSrc = 1 AND 1 = 1. mux বেছে নেয় target = 0x1C + 8 = 0x24. 0x20-এর or x9, x5, x6 কখনো চলে না; পরের instruction 0x24-এর and. offset যোগ হলো এই instruction-এর নিজের PC-র সাথে।
  6. দুই ক্ষেত্রেই। Register file আর মেমরিতে কিছু লেখা হয়নি; শুধু PC বদলাল। দরকার ছিল ২০০ + ৫০ + ১০০ = ৩৫০ ps, অথচ cycle ৬০০ ps — বাকি ২৫০ ps অপেক্ষা।

নিজে চালিয়ে দেখুন — একটি single-cycle simulator

নিচের প্রোগ্রাম এই পর্বের CPU-কে সফটওয়্যারে নকল করে। প্রতিটি instruction-এর জন্য এটি word থেকে field বের করে (পর্ব ২৭-এর decode), opcode দিয়ে control টেবিল দেখে, ImmGen চালায়, তারপর তিনটি mux-এর বাছাই মেনে প্রতিটি তারের মান হিসাব করে। লক্ষ্য করুন, register, মেমরি আর PC-তে লেখা হয় লুপের একদম শেষে — সেটিই clock-এর ধার। প্রোগ্রামের দশটি word পর্ব ২৭-এর নিয়মে আলাদা একটি Python encoder দিয়ে বানানো।

// Single-cycle RV32I subset: one instruction per clock -> fields, control, datapath wires.
const prog = [0x00852283, 0x00C52303, 0x006283B3, 0x00752823, 0x40628433,
  0x00838463, 0x01040413, 0x00838463, 0x0062E4B3, 0x0062F4B3];
const x = new Array(32).fill(0); x[10] = 0x1000;              // x0 stays 0
const mem = { 0x1008: 42, 0x100C: 8 };                         // data memory (words)
const bit = (w, hi, lo) => (w >>> lo) & ((1 << (hi - lo + 1)) - 1);
const sx = (v, n) => (v << (32 - n)) >> (32 - n);              // sign-extend n bits
const hex = (v, n = 2) => "0x" + (v >>> 0).toString(16).toUpperCase().padStart(n, "0");
const CTRL = { // ALUSrc MemtoReg RegWrite MemRead MemWrite Branch ALUOp
  0x33: ["R", "0 0 1 0 0 0 10"], 0x13: ["I", "1 0 1 0 0 0 00"], 0x03: ["I", "1 1 1 1 0 0 00"],
  0x23: ["S", "1 x 0 0 1 0 00"], 0x63: ["B", "0 x 0 0 0 1 01"] };
const immGen = (w, f) => f === "I" ? sx(bit(w, 31, 20), 12)
  : f === "S" ? sx(bit(w, 31, 25) << 5 | bit(w, 11, 7), 12)
  : f === "B" ? sx(bit(w, 31, 31) << 12 | bit(w, 7, 7) << 11 | bit(w, 30, 25) << 5 | bit(w, 11, 8) << 1, 13) : 0;
let pc = 0;
while (pc / 4 < prog.length) {
  const w = prog[pc / 4], op = bit(w, 6, 0), rd = bit(w, 11, 7), f3 = bit(w, 14, 12);
  const rs1 = bit(w, 19, 15), rs2 = bit(w, 24, 20), f7 = bit(w, 31, 25), [fmt, c] = CTRL[op], s = c.split(" ");
  const rd1 = x[rs1], rd2 = x[rs2], imm = immGen(w, fmt), b = s[0] === "1" ? imm : rd2;   // ALUSrc mux
  const opn = s[6] === "00" ? "add" : s[6] === "01" ? "sub"                              // ALU control
    : { "0,0": "add", "0,32": "sub", "7,0": "and", "6,0": "or" }[f3 + "," + f7];
  const alu = { add: rd1 + b, sub: rd1 - b, and: rd1 & b, or: rd1 | b }[opn] | 0, zero = alu === 0 ? 1 : 0;
  const rdata = s[3] === "1" ? (mem[alu] ?? 0) : "-", wb = s[1] === "1" ? rdata : alu;  // MemtoReg mux
  const next = s[5] === "1" && zero ? pc + imm : pc + 4;                                  // PCSrc mux
  const fld = (fmt === "R" || fmt === "I" ? `rd=x${rd} ` : "") + `rs1=x${rs1} ` +
    (fmt === "I" ? "" : `rs2=x${rs2} `) + `f3=${f3}` + (fmt === "R" ? ` f7=${hex(f7)}` : "");
  console.log(`PC=${hex(pc)} ${hex(w, 8)} ${fmt}  ${fld}  ctrl=${c}`);
  console.log(`   RD1=${rd1} RD2=${rd2} imm=${imm} ALU(${opn})=${op === 0x03 || op === 0x23 ? hex(alu, 4) : alu}` +
    ` Zero=${zero} MemData=${rdata} WB=${s[2] === "1" ? wb : "-"} nextPC=${hex(next)}`);
  if (s[4] === "1") mem[alu] = rd2;                              // clock edge: memory write
  if (s[2] === "1" && rd !== 0) x[rd] = wb;                      // clock edge: register write
  pc = next;                                                     // clock edge: PC update
}
console.log(`after the run: x5..x9 = ${x.slice(5, 10).join(", ")}, mem[0x1010] = ${mem[0x1010]}`);

আউটপুট (নির্ধারিত — প্রতিবার একই):

PC=0x00 0x00852283 I  rd=x5 rs1=x10 f3=2  ctrl=1 1 1 1 0 0 00
   RD1=4096 RD2=0 imm=8 ALU(add)=0x1008 Zero=0 MemData=42 WB=42 nextPC=0x04
PC=0x04 0x00C52303 I  rd=x6 rs1=x10 f3=2  ctrl=1 1 1 1 0 0 00
   RD1=4096 RD2=0 imm=12 ALU(add)=0x100C Zero=0 MemData=8 WB=8 nextPC=0x08
PC=0x08 0x006283B3 R  rd=x7 rs1=x5 rs2=x6 f3=0 f7=0x00  ctrl=0 0 1 0 0 0 10
   RD1=42 RD2=8 imm=0 ALU(add)=50 Zero=0 MemData=- WB=50 nextPC=0x0C
PC=0x0C 0x00752823 S  rs1=x10 rs2=x7 f3=2  ctrl=1 x 0 0 1 0 00
   RD1=4096 RD2=50 imm=16 ALU(add)=0x1010 Zero=0 MemData=- WB=- nextPC=0x10
PC=0x10 0x40628433 R  rd=x8 rs1=x5 rs2=x6 f3=0 f7=0x20  ctrl=0 0 1 0 0 0 10
   RD1=42 RD2=8 imm=0 ALU(sub)=34 Zero=0 MemData=- WB=34 nextPC=0x14
PC=0x14 0x00838463 B  rs1=x7 rs2=x8 f3=0  ctrl=0 x 0 0 0 1 01
   RD1=50 RD2=34 imm=8 ALU(sub)=16 Zero=0 MemData=- WB=- nextPC=0x18
PC=0x18 0x01040413 I  rd=x8 rs1=x8 f3=0  ctrl=1 0 1 0 0 0 00
   RD1=34 RD2=0 imm=16 ALU(add)=50 Zero=0 MemData=- WB=50 nextPC=0x1C
PC=0x1C 0x00838463 B  rs1=x7 rs2=x8 f3=0  ctrl=0 x 0 0 0 1 01
   RD1=50 RD2=50 imm=8 ALU(sub)=0 Zero=1 MemData=- WB=- nextPC=0x24
PC=0x24 0x0062F4B3 R  rd=x9 rs1=x5 rs2=x6 f3=7 f7=0x00  ctrl=0 0 1 0 0 0 10
   RD1=42 RD2=8 imm=0 ALU(and)=8 Zero=0 MemData=- WB=8 nextPC=0x28
after the run: x5..x9 = 42, 8, 50, 50, 8, mem[0x1010] = 50

দশটি word থাকলেও চলেছে নয়টি instruction — 0x20-এর or লাফে বাদ পড়েছে, তাই নয়টি clock cycle. প্রথম লাইন-জোড়া উপরের lw stepper-এর সাথে মেলান: RD1 = 4096 (= 0x1000), imm = 8, ALU = 0x1008, MemData = 42, WB = 42. RD2=0 লক্ষ করুন: lw-এর বিট ২৪..২০ = ৮, তাই register file x8 পড়েছে (তখন 0), আর ALUSrc = 1 বলে মানটি উপেক্ষিত। addi-তে বিট ২৪..২০ = ১৬, তাই x16 পড়া হয়েছে। দুটি beq লাইনে stepper-এর ফল: প্রথমটিতে ALU = 16, Zero = 0, nextPC = 0x18; দ্বিতীয়টিতে ALU = 0, Zero = 1, nextPC = 0x24. এখানে imm=8 হলো ImmGen আর Shift left 1 একসাথে — কোড B-format-এর immediate সরাসরি byte-এ জোড়ে। শেষ লাইনে x9 = 8, কারণ ৪২ AND ৮ = 101010 AND 001000 = 001000. চাইলে prog বা mem বদলে নিজের প্রোগ্রাম চালান — নিচের অনুশীলন ৪ ঠিক তাই চায়।

৭ · এক লম্বা clock — critical path আর cycle time-এর হিসাব

Single-cycle CPU-তে clock-এর কাজ সহজ: প্রতিটি উঠন্ত ধারে (rising edge) PC, register file আর data memory নতুন মান নেয়; দুই ধারের মাঝে বাকি সব combinational. তাই পর্ব ১৮-এর নিয়ম সরাসরি খাটে: cycle time হতে হবে সবচেয়ে দীর্ঘ state-থেকে-state পথের সমান বা বেশি — সেই পথই critical path.

Tc ≥ tpcq + (পথের সব combinational delay) + tsetup,   সব instruction-এর পথের মধ্যে সর্বোচ্চটি

পাঠ্যবইয়ের হিসাব

পরীক্ষার সবচেয়ে প্রচলিত সংখ্যা: instruction memory ২০০ ps, register file পড়া ৫০ ps, ALU ১০০ ps, data memory ২০০ ps, register file-এ লেখার setup ৫০ ps; mux, ImmGen, control, PC-র clock-to-Q আর তারের delay শূন্য ধরা; adder ১০০ ps. প্রতিটি শ্রেণির পথ:

শ্রেণিI-memRF পড়াALUD-memRF লেখামোট৬০০ ps-এর মধ্যে অপচয়
R-type২০০৫০১০০—৫০৪০০২০০ (৩৩.৩%)
lw২০০৫০১০০২০০৫০৬০০০
sw২০০৫০১০০২০০—৫৫০৫০ (৮.৩%)
beq২০০৫০১০০——৩৫০২৫০ (৪১.৭%)

beq-এ target adder-এর পথও আছে: ২০০ (I-mem) + ০ (ImmGen) + ১০০ (adder) = ৩০০ ps, যা তুলনার পথের (৩৫০) চেয়ে ছোট, তাই ৩৫০-ই থাকে। PC + 4-এর পথ মাত্র ১০০ ps. সুতরাং critical path হলো lw: instruction memory → register পড়া → ALU → data memory → MemtoReg mux → register file-এর setup. Tc = ৬০০ ps, f = ১ / ৬০০ ps ≈ ১.৬৬৭ GHz. কিন্তু প্রতিটি instruction — এমনকি ৩৫০ ps-এর beq — পায় সেই একই ৬০০ ps.

মূল ধারণা

Single-cycle-এর দুর্বলতা গাণিতিকভাবে সরল: clock period = সর্বোচ্চ(প্রতিটি instruction-এর সময়), অথচ গড় দরকার এর চেয়ে কম। অপচয় = Tc − গড় দরকার। একটি ধীর instruction (এখানে lw) সব instruction-কে ধীর করে দেয় — “make the common case fast” নীতির ঠিক উল্টো।

Instruction mix আর “পরিবর্তনশীল clock”-এর কল্পনা

একটি প্রোগ্রামে ধরুন ২৫% lw, ১০% sw, ৪৫% R-type, ২০% beq (instruction mix). গড় দরকার = ০.২৫ × ৬০০ + ০.১০ × ৫৫০ + ০.৪৫ × ৪০০ + ০.২০ × ৩৫০ = ১৫০ + ৫৫ + ১৮০ + ৭০ = ৪৫৫ ps. অর্থাৎ গড়ে প্রতিটি cycle-এর ১৪৫ ps (২৪.২%) অপেক্ষায় যায়। যদি কোনোভাবে প্রতিটি instruction-কে ঠিক তার দরকারি সময় দেওয়া যেত (পরিবর্তনশীল দৈর্ঘ্যের clock), speedup হতো ৬০০ / ৪৫৫ ≈ ১.৩২ গুণ। বাস্তবে এমন clock বানানো কঠিন (ভাবনার প্রশ্ন ৩), কিন্তু হিসাবটি বলে দেয় কোথায় লাভ লুকিয়ে আছে।

Iron law দিয়ে পুরো ছবি: CPU time = instruction count × CPI × Tc. Single-cycle-এ CPI = ১ (সবচেয়ে কম সম্ভব), কিন্তু Tc = ৬০০ ps (সবচেয়ে বেশি)। ১০০ কোটি instruction-এ সময় ১০৯ × ১ × ৬০০ ps = ০.৬ সেকেন্ড। CPI কম দেখে বিভ্রান্ত হওয়া চলবে না।

নিচের calculator যেকোনো delay-এর সেট থেকে এই হিসাব করে। দ্বিতীয় সেটে বাস্তবের মতো কিছু খরচ যোগ করা হয়েছে — PC-র clock-to-Q ৩০, ImmGen ৩০, প্রতিটি mux ২০, register setup ২০ ps — দেখুন critical path কীভাবে mux-এর পথ ধরে:

// Single-cycle timing: time each instruction class needs; clock period = the slowest one.
function classTimes(d) {
  const front = d.pcq + d.imem;                       // PC -> instruction bits ready
  const bViaMux = d.rf + d.mux;                        // ReadData2 -> ALUSrc mux -> ALU
  const immPath = Math.max(d.rf, d.imm + d.mux);       // rs1 read vs ImmGen -> mux
  return {
    "R-type": front + bViaMux + d.alu + d.mux + d.rfSetup,
    "lw":     front + immPath + d.alu + d.dmem + d.mux + d.rfSetup,
    "sw":     front + immPath + d.alu + d.dmem,
    "beq":    front + Math.max(bViaMux + d.alu, d.imm + d.add) + d.mux + d.pcSetup };
}
const mix = { "R-type": 0.45, lw: 0.25, sw: 0.10, beq: 0.20 };   // instruction mix
function report(name, d) {
  const t = classTimes(d), Tc = Math.max(...Object.values(t));
  const pc4 = d.pcq + d.add + d.mux + d.pcSetup;
  console.log(`${name}: Tc = ${Tc} ps, f = ${(1000 / Tc).toFixed(3)} GHz (PC+4 path ${pc4} ps)`);
  for (const [k, v] of Object.entries(t))
    console.log(`  ${k.padEnd(6)} needs ${String(v).padStart(3)} ps, wasted ${String(Tc - v).padStart(3)} ps` +
      ` (${(100 * (Tc - v) / Tc).toFixed(1)}% of the cycle)`);
  const avg = Object.entries(mix).reduce((s, [k, p]) => s + p * t[k], 0);
  console.log(`  mix: average need ${avg.toFixed(1)} ps, average waste ${(Tc - avg).toFixed(1)} ps` +
    ` (${(100 * (Tc - avg) / Tc).toFixed(1)}%)`);
  console.log(`  ideal variable-length clock: speedup = ${Tc} / ${avg.toFixed(1)} = ${(Tc / avg).toFixed(3)}x`);
}
report("A (textbook units)", { pcq: 0, imem: 200, rf: 50, imm: 0, mux: 0, alu: 100,
  dmem: 200, rfSetup: 50, add: 100, pcSetup: 0 });
report("B (with register and mux overheads)", { pcq: 30, imem: 200, rf: 50, imm: 30, mux: 20, alu: 100,
  dmem: 200, rfSetup: 20, add: 100, pcSetup: 20 });

আউটপুট:

A (textbook units): Tc = 600 ps, f = 1.667 GHz (PC+4 path 100 ps)
  R-type needs 400 ps, wasted 200 ps (33.3% of the cycle)
  lw     needs 600 ps, wasted   0 ps (0.0% of the cycle)
  sw     needs 550 ps, wasted  50 ps (8.3% of the cycle)
  beq    needs 350 ps, wasted 250 ps (41.7% of the cycle)
  mix: average need 455.0 ps, average waste 145.0 ps (24.2%)
  ideal variable-length clock: speedup = 600 / 455.0 = 1.319x
B (with register and mux overheads): Tc = 620 ps, f = 1.613 GHz (PC+4 path 170 ps)
  R-type needs 440 ps, wasted 180 ps (29.0% of the cycle)
  lw     needs 620 ps, wasted   0 ps (0.0% of the cycle)
  sw     needs 580 ps, wasted  40 ps (6.5% of the cycle)
  beq    needs 440 ps, wasted 180 ps (29.0% of the cycle)
  mix: average need 499.0 ps, average waste 121.0 ps (19.5%)
  ideal variable-length clock: speedup = 620 / 499.0 = 1.242x

সেট A উপরের টেবিলের সাথে হুবহু মেলে: ৬০০ ps, গড় ৪৫৫, অপচয় ১৪৫ ps (২৪.২%), সম্ভাব্য speedup ১.৩১৯. সেট B-তে lw = ৩০ + ২০০ + সর্বোচ্চ(৫০, ৩০ + ২০) + ১০০ + ২০০ + ২০ + ২০ = ৬২০ ps. এখানে একটি সূক্ষ্মতা: lw-এ ALU-র A ইনপুট আসে সরাসরি RD1 থেকে, আর B আসে ImmGen → ALUSrc mux হয়ে; দুটি পথই ৫০ ps, তাই max. R-type-এ RD2-কে mux পেরোতে হয় (৫০ + ২০)। খেয়াল করুন, overhead যোগ হলে ছোট instruction-গুলোর আনুপাতিক অপচয় কমে আসে (২৯%), কারণ প্রতিটি পথেই একই স্থির খরচ (tpcq, setup) বসে — এই স্থির খরচই sequencing overhead, যা pipelining-এর সীমাও ঠিক করবে (পর্ব ৩৫)। কোডে প্রতিটি শ্রেণির পথ হাতে লেখা যোগফল; বড় নকশায় একই কাজ করে static timing analysis টুল।

Static timing টুল জানে না কোন পথ কোন instruction-এর। তার কাছে RD2 → ALUSrc mux → ALU → data memory → MemtoReg mux → register file-ও একটি পথ, যদিও কোনো instruction এটি ব্যবহার করে না (R-type মেমরি ছোঁয় না, lw RD2 নেয় না)। সেট B-তে এই পথ ৩০ + ২০০ + ৭০ + ১০০ + ২০০ + ৪০ = ৬৪০ ps. নকশাকার একে “false path” ঘোষণা না করলে টুল clock period ৬৪০ ps ধরবে, অর্থাৎ clock অকারণে ধীর হবে। পরীক্ষায় সাধারণত instruction ধরে পথ হিসাব করতে বলা হয় — প্রশ্ন ভালো করে পড়ুন।

এই অপচয়ের দুটি জবাব আছে, আর দুটিই এই মডিউলের পরের পর্ব। পর্ব ৩৪-এর multi-cycle নকশা instruction-কে কয়েকটি ছোট clock-এ ভাগ করে, যাতে beq কম cycle আর lw বেশি cycle নেয়, আর একটি ALU বা একটি মেমরি একাধিক ধাপে ভাগাভাগি করা যায়। পর্ব ৩৫-এর pipelining একই ছোট ধাপগুলোতে একসাথে কয়েকটি instruction চালায় — গার্মেন্টস কারখানার assembly line-এর মতো। দুটিরই শুরু এই হিসাব থেকে: single-cycle-এর ৬০০ ps-কে ভাঙতে হবে।

৮ · নতুন instruction যোগ — addi, jal আর ImmGen-এর ভূমিকা

একটি datapath-এর গুণ বোঝা যায় নতুন instruction যোগ করতে কত কম বদল লাগে তা দেখে। পদ্ধতি সবসময় একই: (১) instruction-টির কাজ register transfer-এ লিখুন, (২) দেখুন কোন মান কোন unit-এর ইনপুটে নতুন উৎস থেকে আসছে, (৩) সেখানে mux-এর নতুন ইনপুট বা নতুন mux বসান, (৪) control টেবিলে নতুন সারি আর দরকারে নতুন সংকেত যোগ করুন।

addi — কোনো নতুন hardware নয়

addi rd, rs1, imm: rd ← rs1 + imm. ALU-র B-তে immediate (lw-এর মতো, ALUSrc = 1), কিন্তু লেখা হয় ALU-র ফল (R-type-এর মতো, MemtoReg = 0). দুটি পথই আগে থেকে আছে; শুধু control-এ একটি নতুন সারি: ALUSrc = 1, MemtoReg = 0, RegWrite = 1, MemRead = 0, MemWrite = 0, Branch = 0, ALUOp = 00. ImmGen I-format আগেই জানে (lw-এর জন্য)। সময়: ২০০ + ৫০ + ১০০ + ৫০ = ৪০০ ps, R-type-এর সমান।

jal — তিনটি ছোট বদল

jal rd, offset: rd ← PC + 4, আর PC ← PC + offset (J-format, ±১ MiB). তিনটি জিনিস নতুন:

  1. ImmGen-এ J-format: inst[31], inst[19:12], inst[20], inst[30:21] জুড়ে ২১-বিট offset (পর্ব ২৭-এর টেবিল)। ImmGen-এর ভেতরের mux-এ আরেকটি পছন্দ।
  2. লেখার মানের নতুন উৎস: register file-এ যাবে PC + 4, যা ALU বা মেমরি থেকে আসে না। তাই MemtoReg mux-কে ৩-ইনপুটের করতে হয় (ALU, Mem, PC + 4), select ২ বিটের। Harris & Harris-এর বই এই চওড়া সংকেতকে ResultSrc বলে।
  3. PC-র সিদ্ধান্ত: jal সবসময় লাফায়। target adder (PC + imm) আগে থেকেই আছে, শুধু সিদ্ধান্তের logic বদলায়: PCSrc = (Branch AND Zero) OR Jump. নতুন control সংকেত Jump.

উদাহরণ: PC = 0x30-এ jal x1, 2048 (0x001000EF) — x1 ← 0x34, PC ← 0x30 + 0x800 = 0x830. সময়: target = ২০০ + ১০০ = ৩০০ ps, PC + 4 → register = ১০০ + mux + setup ৫০ = ১৫০ ps — critical path বাড়ে না। jalr যোগ করলে PC-র তৃতীয় উৎস লাগে — rs1 + imm, যা ALU-র ফল — তাই PCSrc mux-ও তিন-ইনপুটের হয়; lui-এর জন্য ImmGen-এ U-format আর ALU-কে immediate “পার করে দেওয়া”-র ব্যবস্থা; auipc-এর জন্য ALU-র A ইনপুটে PC আনার mux. প্রতিটি নতুন instruction মানে একটু বেশি mux, একটু চওড়া control — কিন্তু datapath-এর মূল কাঠামো একই থাকে।

ImmGen-কে কাজে দেখুন

Datapath-এর তিনটি mux-ই সহজ; সবচেয়ে বেশি বিট-জোড়ার কাজ ImmGen-এ। নিচের প্রোগ্রাম ছয়টি জানা word থেকে immediate বের করে sign extend করে, Patterson–Hennessy-এর ধাঁচে — B-format-এ ImmGen দেয় imm[12:1], তারপর Shift left 1:

// ImmGen: pull the immediate out of an I, S or B word and sign-extend it to 32 bits.
const bit = (w, hi, lo) => (w >>> lo) & ((1 << (hi - lo + 1)) - 1);
const sx = (v, n) => (v << (32 - n)) >> (32 - n);                  // copy bit n-1 upward
const hex8 = v => "0x" + (v >>> 0).toString(16).toUpperCase().padStart(8, "0");
const grp = (v, n) => v.toString(2).padStart(n, "0").replace(/(?=(\d{4})+$)/g, " ").trim();
function immGen(w) {                  // returns [format, 12 raw bits, where they came from]
  const op = bit(w, 6, 0);
  if (op === 0x03 || op === 0x13) return ["I", bit(w, 31, 20), "inst[31:20]"];
  if (op === 0x23) return ["S", bit(w, 31, 25) << 5 | bit(w, 11, 7), "inst[31:25] + inst[11:7]"];
  if (op === 0x63) return ["B", bit(w, 31, 31) << 11 | bit(w, 7, 7) << 10 | bit(w, 30, 25) << 4 |
    bit(w, 11, 8), "inst[31] + inst[7] + inst[30:25] + inst[11:8]"];
  return ["?", 0, "no immediate"];
}
const words = [[0x00852283, "lw   x5, 8(x10)"], [0xFFF00293, "addi x5, x0, -1"],
  [0x00752823, "sw   x7, 16(x10)"], [0xFE992E23, "sw   x9, -4(x18)"],
  [0x00838463, "beq  x7, x8, 8"], [0xFEB50CE3, "beq  x10, x11, -8"]];
for (const [w, asm] of words) {
  const [f, raw, from] = immGen(w), ext = sx(raw, 12);
  console.log(`${hex8(w)}  ${asm.padEnd(17)} ${f}: ${from}`);
  let line = `    12 bits ${grp(raw, 12)} -> sign-extended ${hex8(ext)} = ${ext}`;
  if (f === "B") line += ` (imm[12:1]); <<1 -> byte offset ${ext * 2}`;
  console.log(line);
}

আউটপুট:

0x00852283  lw   x5, 8(x10)   I: inst[31:20]
    12 bits 0000 0000 1000 -> sign-extended 0x00000008 = 8
0xFFF00293  addi x5, x0, -1   I: inst[31:20]
    12 bits 1111 1111 1111 -> sign-extended 0xFFFFFFFF = -1
0x00752823  sw   x7, 16(x10)  S: inst[31:25] + inst[11:7]
    12 bits 0000 0001 0000 -> sign-extended 0x00000010 = 16
0xFE992E23  sw   x9, -4(x18)  S: inst[31:25] + inst[11:7]
    12 bits 1111 1111 1100 -> sign-extended 0xFFFFFFFC = -4
0x00838463  beq  x7, x8, 8    B: inst[31] + inst[7] + inst[30:25] + inst[11:8]
    12 bits 0000 0000 0100 -> sign-extended 0x00000004 = 4 (imm[12:1]); <<1 -> byte offset 8
0xFEB50CE3  beq  x10, x11, -8 B: inst[31] + inst[7] + inst[30:25] + inst[11:8]
    12 bits 1111 1111 1100 -> sign-extended 0xFFFFFFFC = -4 (imm[12:1]); <<1 -> byte offset -8

তিনটি জিনিস লক্ষ করুন। (১) sw x9, -4(x18) আর beq x10, x11, -8-এর ImmGen আউটপুট একই (0xFFFFFFFC = −৪), কিন্তু অর্থ আলাদা: store-এ −৪ byte, branch-এ −৪ halfword, Shift left 1-এর পরে −৮ byte. (২) sign extension মানে বিট ১১-এর কপি ওপরের ২০ বিটে — sx() ফাংশন ২০ ঘর বাঁয়ে সরিয়ে আবার signed ডানে সরায় (>> চিহ্ন বিট টেনে আনে)। (৩) প্রতিটি format-এ sign আসে inst[31] থেকে, তাই hardware-এ ওপরের ২০ বিটের তার format জানার আগেই ঠিক হয়ে যায়। Word-গুলোর decode পর্ব ২৭-এর টেবিলের সাথে মিলিয়ে দেখুন।

আরও গভীরে: বাস্তবে single-cycle নকশা বাণিজ্যিক CPU-তে প্রায় দেখা যায় না; এটি শেখার আর FPGA ল্যাবের নকশা। একটি কারণ মেমরি নিজেই: chip-এর SRAM আর FPGA-র block RAM সাধারণত synchronous — ঠিকানা clock-এর ধারে নেয়, ডেটা দেয় পরের দিকে। তাই “এক cycle-এ ঠিকানা দিয়ে একই cycle-এ ডেটা” মডেলটি বাস্তবায়নে FPGA-তে ছোট LUT-ভিত্তিক মেমরি বা নামন্ত ধারে পড়ার মতো কৌশল লাগে। অন্যদিকে ছোট microcontroller-ও pipelined: ARM Cortex-M0-এ তিন ধাপের, Cortex-M0+-এ দুই ধাপের pipeline বলে ARM জানায়; ক্ষুদ্র RISC-V core-গুলোর অনেকগুলোতেও দুই-তিন ধাপ। “Single-cycle execution” বিজ্ঞাপনের ভাষায় সাধারণত বোঝায় throughput প্রতি cycle-এ একটি — ভেতরে fetch আর execute আলাদা ধাপে চলে।

পরীক্ষা ও ইন্টারভিউতে এই বিষয়ে প্রায়ই যা জিজ্ঞেস করা হয়:

  • Single-cycle datapath-এ critical path কোনটি? — lw: I-mem → register পড়া → ALU → D-mem → mux → register setup; পাঠ্যবই-সংখ্যায় ৬০০ ps.
  • Instruction আর data memory আলাদা কেন? — এক cycle-এ প্রতিটি unit একবারই ব্যবহার হয়, অথচ lw-কে দুবার মেমরি ছুঁতে হয়।
  • sw-এ MemtoReg কেন X? — RegWrite = 0, তাই register file-এ কী পৌঁছাল তাতে কিছু যায় আসে না।
  • PCSrc কীভাবে তৈরি হয়? — Branch AND Zero; Zero আসে ALU-র rs1 − rs2 থেকে।
  • CPI = ১ হলে single-cycle সবচেয়ে দ্রুত নয় কেন? — Tc ঠিক করে সবচেয়ে ধীর instruction; গড় instruction-এর দরকার এর চেয়ে কম, বাকিটা অপচয়।

৯ · AI-এর যুগে — datapath-এর উত্তর যাচাই

Datapath-এর প্রশ্নে AI সাধারণত আত্মবিশ্বাসী আর সুন্দর উত্তর দেয় — একটি টেবিল, একটি ছবির বর্ণনা, একটি সংখ্যা। কিন্তু এই বিষয়ে পাঠ্যবইয়ের দুই প্রজন্ম (MIPS আর RISC-V) আর অনেক রকম নকশা-রীতি মিশে আছে, তাই ভুলও সূক্ষ্ম। সুখবর: প্রায় সবকিছু হাতে বা ছোট একটি simulator-এ যাচাই করা যায়।

AI প্রায়ই যা করেসত্যিটাযাচাইয়ের উপায়
RISC-V datapath-এ RegDst mux আর “Shift left 2” বসায়ওগুলো MIPS-এর; RISC-V-এ rd এক জায়গায়, branch offset halfword-এPatterson–Hennessy RISC-V সংস্করণের ছবি, RISC-V manual
branch target = PC + 4 + offset বলেRISC-V-এ target = এই instruction-এর PC + offsetএকটি beq হাতে বা simulator-এ trace
Tc = সব unit-এর delay-এর যোগফল (দুই adder-সহ)Tc = সবচেয়ে দীর্ঘ একটি পথ; সমান্তরাল পথে max, যোগ নয়প্রতিটি শ্রেণির পথ আলাদা লিখে যোগ
sw-এ RegWrite = 1 বা MemtoReg-এর নির্দিষ্ট মান দাবিRegWrite = 0, MemtoReg = X“এই সংকেত ভুল হলে কী ভাঙবে?” প্রশ্ন নিজেকে
x0-এ লেখা হলে x0 বদলে যায় ধরে নেয়x0 সবসময় 0; লেখা উপেক্ষিতregister file-এর Verilog বা simulator
CPI = ১ মানে “সবচেয়ে দ্রুত প্রসেসর”CPU time = IC × CPI × Tc; Tc লম্বাiron law দিয়ে সংখ্যায় তুলনা

AI-কে datapath-এর প্রশ্ন করার সময় ISA আর বইয়ের রীতি স্পষ্ট বলে দিন, আর প্রতিটি সংখ্যার হিসাব চেয়ে নিন:

আমি Patterson–Hennessy RISC-V সংস্করণের single-cycle datapath (MIPS নয়) শিখছি। subset: add, sub, and, or, lw, sw, beq. Unit delay: I-mem 250 ps, register read 150 ps, ALU 200 ps, D-mem 250 ps, register setup 20 ps, PC clk-to-Q 30 ps, mux 25 ps, ImmGen 35 ps, adder 150 ps. ১. প্রতিটি instruction শ্রেণির পথ unit ধরে লেখো, সমান্তরাল পথে কোথায় max নিচ্ছ তা দেখাও। ২. clock period আর frequency বের করো। ৩. lw, sw, beq-এর control signal (ALUSrc, MemtoReg, RegWrite, MemRead, MemWrite, Branch, ALUOp) একটি টেবিলে দাও; X কোথায় আর কেন, তা বলো। ৪. হিসাব যাচাইয়ের জন্য একটি ছোট JavaScript ফাংশন দাও যা delay থেকে প্রতিটি শ্রেণির সময় বের করে — আমি নিজে চালিয়ে মেলাব। ৫. কোনো ধাপে MIPS-এর রীতি এসে গেলে বা নিশ্চিত না হলে স্পষ্ট করে বলো।

AI-এর উত্তর হাতে পেলে — যাচাইয়ের চেকলিস্ট

ভাবনার প্রশ্ন

প্রতিটি প্রশ্ন নিয়ে নিজে কিছুক্ষণ ভাবুন — তারপর “→ উত্তর” চাপুন।

প্র ০১Single-cycle CPU এত অদক্ষ হলে পাঠ্যবই কেন এটি দিয়েই শুরু করে? বাস্তবে কি কেউ এমন CPU বানিয়েছে?

পাঠ্যবইয়ের যুক্তি শিক্ষাগত, প্রকৌশলগত নয়। Single-cycle নকশায় প্রতিটি instruction-এর কাজ একটি ছবিতে, একটি cycle-এ দেখা যায় — কোনো instruction আরেকটির সাথে মেশে না, কোনো মধ্যবর্তী register নেই, কোনো hazard নেই। ফলে “কোন instruction কোন unit ব্যবহার করে আর কেন” — এই মৌলিক প্রশ্নটি পরিষ্কারভাবে শেখা যায়। পরে multi-cycle (পর্ব ৩৪) আর pipelining (পর্ব ৩৫)-এ এই একই datapath-কে কেটে ধাপে ভাগ করা হয়; মূল ছবি মাথায় না থাকলে সেই কাটাকাটি বোঝা কঠিন।

বাস্তবের ইতিহাস ভিন্ন। প্রথম যুগের কম্পিউটার — যেমন ১৯৪৯ সালের EDSAC — instruction-কে কয়েকটি ধাপে, প্রায়ই বিট-বিট করে (serial) চালাত; “একটি লম্বা cycle-এ পুরো instruction” মডেল তাদের ছিল না। ১৯৮০-এর দশকের মাঝামাঝি MIPS R2000-এর মতো বাণিজ্যিক RISC প্রসেসর সরাসরি পাঁচ ধাপের pipeline নিয়ে আসে। অর্থাৎ পাঠ্যবইয়ের single-cycle CPU একটি মানসিক মডেল, যা ইতিহাসের কোনো নির্দিষ্ট যন্ত্রের নকল নয়।

তবু এটি অকেজো নয়। বিশ্বের অনেক বিশ্ববিদ্যালয়ের ল্যাবে — বাংলাদেশের কিছু CSE/EEE ল্যাবসহ — শিক্ষার্থীরা FPGA-তে প্রথমে একটি single-cycle RISC-V বা MIPS core বানায়, simulation-এ প্রতিটি তারের মান দেখে, তারপর pipeline যোগ করে। ধীর clock-এ (যেমন কয়েক দশ MHz) single-cycle core দিব্যি কাজ করে, আর debug করা সবচেয়ে সহজ। আবার ছোট AVR-এর মতো microcontroller-এর বিজ্ঞাপনে “single-cycle execution” কথাটি থাকে, কিন্তু সেখানে ভেতরে fetch আর execute দুটি ধাপে overlap হয় — throughput প্রতি cycle-এ একটি, এটুকুই অর্থ।

আরেকটি গুরুত্বপূর্ণ ব্যবহার: reference model. বড় কোম্পানি pipelined বা out-of-order core যাচাই করার সময় একটি সরল, “এক instruction এক ধাপ” simulator-এর সাথে প্রতিটি instruction-এর পর register-এর মান মেলায়। এই পর্বের JS simulator ঠিক সেই ধরনের মডেলের ক্ষুদ্র রূপ।

মূল উপলব্ধি: Single-cycle নকশা দ্রুত CPU বানানোর উপায় নয়, CPU বোঝার উপায়। সঠিকতার মানদণ্ড হিসেবে এটি বাস্তব শিল্পেও বেঁচে আছে।

প্র ০২Datapath-এ instruction আর data memory আলাদা। অথচ আমাদের কম্পিউটারে RAM তো একটিই — তাহলে কোনটি সত্যি?

দুটিই সত্যি, ভিন্ন স্তরে। Single-cycle datapath-এ দুটি মেমরি লাগে একটি কঠোর কারণে: এক cycle-এ প্রতিটি hardware unit একবারই ব্যবহার করা যায়। lw-কে একই cycle-এ instruction আনতে হয় আর ডেটা পড়তে হয় — একটি single-port মেমরি একসাথে দুটি ঠিকানা দিতে পারে না। দুটি port-এর মেমরি সম্ভব, কিন্তু বড় আর ধীর। তাই পাঠ্যবই Harvard-ধাঁচ বেছে নেয়।

অন্যদিকে von Neumann-এর stored-program ধারণায় (১৯৪৫-এর EDVAC প্রতিবেদন) instruction আর ডেটা একই মেমরিতে থাকে — এর বড় সুবিধা হলো প্রোগ্রামও ডেটা: compiler একটি প্রোগ্রাম লিখে ফাইলে রাখে, OS তা মেমরিতে load করে, JIT compiler চলার সময় নতুন machine code বানায়। আলাদা মেমরি হলে এগুলো কঠিন হতো। ১৯৭৭ সালে John Backus তাঁর Turing Award বক্তৃতায় CPU আর একক মেমরির মাঝের সরু পথকে “von Neumann bottleneck” বলেন — এক পথ দিয়ে instruction আর ডেটা দুটিকেই আসতে হয়।

আধুনিক CPU দুটির মাঝামাঝি পথ নেয়, যাকে অনেকে “modified Harvard” বলেন। Main memory (DRAM) একটিই, L2/L3 cache-ও সাধারণত একসাথে, কিন্তু CPU-র সবচেয়ে কাছের L1 cache দুই ভাগে: L1 instruction cache আর L1 data cache (পর্ব ৪৪-৪৫)। Pipeline-এর fetch ধাপ আর memory ধাপ তাই একই cycle-এ দুই জায়গা থেকে পড়তে পারে — ঠিক single-cycle datapath-এর দুই মেমরির মতো।

এর একটি ফল প্রোগ্রামারের চোখেও পড়ে। কোনো প্রোগ্রাম মেমরিতে নতুন instruction লিখলে (JIT, debugger-এর breakpoint), সেই লেখা প্রথমে D-cache-এ যায়; I-cache পুরোনো কপি রেখে দিতে পারে। RISC-V-এ তাই fence.i instruction আছে, যা নিশ্চিত করে আগের store-গুলো পরের instruction fetch-এ দেখা যাবে। x86-এ এই কাজ বেশিরভাগ hardware নিজে সামলায়, ARM-এ cache পরিষ্কারের বিশেষ instruction লাগে।

মূল উপলব্ধি: “এক মেমরি না দুই মেমরি” প্রশ্নের উত্তর স্তরভেদে আলাদা — প্রোগ্রামারের কাছে একটি ঠিকানা-জগৎ, CPU-র কাছে দুটি আলাদা দ্রুত পথ।

প্র ০৩প্রতিটি instruction-কে তার দরকারমতো সময় দিলে ১.৩২ গুণ দ্রুত হতো। তাহলে পরিবর্তনশীল দৈর্ঘ্যের clock কেউ বানায় না কেন?

ধারণাটি লোভনীয়, আর আসলে নতুনও নয়। সমস্যা তিনটি। প্রথমত, clock সংকেত বানায় একটি crystal oscillator আর PLL (পর্ব ১৮), যা একটি নির্দিষ্ট ছন্দে চলে। প্রতি cycle-এ period বদলাতে হলে instruction decode হওয়ার পরই ঠিক করতে হবে এই cycle কত লম্বা — অথচ decode নিজেই cycle-এর ভেতরের কাজ। একটি “ঘড়ি যা নিজের পরের টিক নিজে ঠিক করে” বানানো জটিল আর ঝুঁকিপূর্ণ।

দ্বিতীয়ত, delay স্থির নয়। একই ALU-র delay তাপমাত্রা, ভোল্টেজ আর chip-এর উৎপাদন-তারতম্যে বদলায়; একটি যোগে carry কত দূর যাবে তা ডেটার ওপরও নির্ভর করে। নির্দিষ্ট clock-এর নকশায় সবচেয়ে খারাপ ক্ষেত্র ধরে একবার margin রাখা হয়; পরিবর্তনশীল clock-এ প্রতিটি instruction শ্রেণির জন্য এই margin আলাদা করে যাচাই করতে হতো।

তৃতীয়ত — এবং সবচেয়ে গুরুত্বপূর্ণ — একটি ভালো বিকল্প আছে। Multi-cycle নকশা (পর্ব ৩৪) একই লক্ষ্য পূরণ করে নির্দিষ্ট clock দিয়েই: ছোট clock, আর প্রতিটি instruction দরকারমতো কয়েকটি cycle নেয় — beq কম, lw বেশি। এটি “পরিবর্তনশীল সময়”-কে clock-এর পূর্ণসংখ্যা গুণিতকে রূপ দেয়, আর এর সাথে unit ভাগাভাগির সুবিধাও আসে। Pipelining আরও এক ধাপ এগিয়ে সময়ের অপচয়কে overlap দিয়ে ঢেকে দেয়।

তবে clock ছাড়া circuit-এর গবেষণা বহু দশকের: asynchronous বা self-timed নকশায় প্রতিটি ধাপ কাজ শেষ হলে “শেষ” সংকেত পাঠায়। ১৯৯০-এর দশকে University of Manchester-এ Steve Furber-এর দল AMULET নামে asynchronous ARM-সামঞ্জস্যপূর্ণ প্রসেসর বানিয়েছিল। এগুলো কাজ করেছে, কিন্তু নকশা ও যাচাইয়ের টুল মূলত synchronous নকশার জন্য তৈরি বলে মূলধারায় আসেনি। আজকের chip-এ এর আত্মীয় হলো clock gating আর DVFS — কাজ অনুযায়ী clock বন্ধ রাখা বা ভোল্টেজ-frequency বদলানো, তবে instruction-প্রতি নয়, মাইক্রোসেকেন্ড-মিলিসেকেন্ডের স্কেলে।

মূল উপলব্ধি: প্রকৌশলে সেরা ধারণা সবসময় সরাসরি বাস্তবায়ন হয় না; একই লাভ আরও নিরাপদ পথে — ছোট নির্দিষ্ট clock আর বেশি cycle — তুলে আনা যায়।

প্র ০৪Patterson–Hennessy-এর পুরোনো MIPS datapath আর নতুন RISC-V datapath-এর মধ্যে কী কী পার্থক্য, আর সেগুলো ISA-র কোন সিদ্ধান্ত থেকে এল?

বইটির MIPS সংস্করণগুলোয় যে ছবি কয়েক প্রজন্মের শিক্ষার্থী মুখস্থ করেছেন, ২০১৭ সালের RISC-V সংস্করণে তার কয়েকটি অংশ বদলে গেছে, আর প্রতিটি বদলের পেছনে ISA-র encoding-এর একটি সিদ্ধান্ত আছে।

RegDst mux বাদ: MIPS-এ R-type-এর ফল যায় rd field-এ (বিট ১৫..১১), কিন্তু I-type (যেমন lw)-এর ফল যায় rt field-এ (বিট ২০..১৬)। তাই লেখার register বাছতে একটি mux আর RegDst সংকেত লাগত। RISC-V-এ rd যে format-এ আছে, সবখানে বিট ১১..৭-এ (পর্ব ২৭), তাই mux-টিই নেই।

Sign-extend থেকে ImmGen: MIPS-এ immediate সবসময় বিট ১৫..০-তে, তাই একটি সরল ১৬ → ৩২ sign-extender যথেষ্ট ছিল। RISC-V-এ I, S, B (আর পরে U, J) format-এ বিট ছড়ানো, তাই opcode দেখে বিট জোড়ার একটি ImmGen লাগে — একটু জটিল, কিন্তু sign সবসময় inst[31]-এ থাকায় দ্রুত।

Shift left 2 থেকে Shift left 1: MIPS-এর branch offset গোনা হতো word-এ (৪ byte), তাই ২ ঘর সরাতে হতো। RISC-V-এর offset halfword-এ, যাতে ভবিষ্যতের ১৬-বিট C extension-এর instruction-ও লক্ষ্য হতে পারে; তাই ১ ঘর।

PC-relative-এর ভিত্তি: MIPS-এ target = PC + 4 + offset × ৪, RISC-V-এ PC + offset. আর বাস্তব MIPS-এ branch delay slot ছিল (branch-এর পরের instruction সবসময় চলত), যা pipeline-এর একটি পুরোনো সমাধান; RISC-V ইচ্ছা করে এটি রাখেনি, কারণ আধুনিক branch prediction-এ এর লাভ কম আর জটিলতা বেশি (পর্ব ৩৭)।

Jump: MIPS-এর j ২৬-বিট field-কে PC-র ওপরের ৪ বিটের সাথে জুড়ত (pseudo-direct); RISC-V-এর jal PC-relative আর ফেরার ঠিকানাও rd-তে রাখে।

মূল উপলব্ধি: datapath-এর প্রতিটি mux আর বাক্স ISA-র কোনো না কোনো সিদ্ধান্তের ছায়া। Encoding একটু ভালো সাজালে hardware থেকে একটি গোটা mux আর একটি control সংকেত মুছে যায়।

প্র ০৫Critical path কি সবসময় lw? একটি গুণের instruction (mul) single-cycle CPU-তে যোগ করলে কী হবে?

না, lw critical path কারণ পাঠ্যবইয়ের সংখ্যায় মেমরি ধীর আর lw একমাত্র instruction যা দুই মেমরি আর ALU তিনটিই পরপর ব্যবহার করে। সংখ্যা বদলালে উত্তরও বদলায়। যেমন ধরুন data memory খুব দ্রুত (একটি ছোট cache, ৫০ ps) কিন্তু ALU ধীর (৩০০ ps). তখন lw = ২০০ + ৫০ + ৩০০ + ৫০ + ৫০ = ৬৫০, আর R-type = ২০০ + ৫০ + ৩০০ + ৫০ = ৬০০ — এখনো lw, কিন্তু ব্যবধান মাত্র ৫০ ps.

এবার RV32M-এর mul যোগ করুন, আর ধরুন একটি ৩২ × ৩২ বিটের combinational multiplier-এর delay ৬০০ ps (পর্ব ২১-এর দ্রুত multiplier-ও একটি adder-এর কয়েকগুণ সময় নেয়)। mul-এর পথ = ২০০ + ৫০ + ৬০০ + ৫০ = ৯০০ ps. এখন Tc = ৯০০ ps — সব instruction দেড় গুণ ধীর, অথচ সাধারণ প্রোগ্রামে mul হয়তো কয়েক শতাংশ। ভাগ (division) বা floating-point instruction যোগ করলে অবস্থা আরও খারাপ: এগুলো সাধারণত বহু cycle-এর কাজ।

এখানেই Amdahl-এর সূত্রের (পর্ব ০৩) একটি উল্টো শিক্ষা: একটি বিরল কাজকে জায়গা দিতে গিয়ে সাধারণ কাজকে ধীর করা। ভালো নকশার নীতি — “make the common case fast” — বলে, ধীর আর বিরল কাজকে আলাদা পথে, একাধিক cycle-এ চলতে দিন। বাস্তব CPU ঠিক এটাই করে: multiplier-কে কয়েকটি pipeline ধাপে ভাগ করে (৩–৪ cycle latency), divider-কে iterative বানায় (দশ বা তারও বেশি cycle), আর বাকি instruction ছোট clock-এ চলে।

Single-cycle নকশায় এই স্বাধীনতা নেই, কারণ সব instruction-কে একই এক cycle-এ শেষ হতে হবে। এটিই multi-cycle (পর্ব ৩৪) আর pipeline (পর্ব ৩৫)-এর সবচেয়ে শক্ত যুক্তি: instruction-এর সময়কে clock থেকে আলাদা করা।

মূল উপলব্ধি: Critical path delay-এর সংখ্যার ফল, কোনো স্থির নিয়ম নয়; আর single-cycle নকশায় যে instruction সবচেয়ে ধীর, সে সবার শাসক।

অনুশীলন

খাতা-কলম নিন; প্রতিটি পথ unit ধরে লিখে যোগ করুন, তারপর উপরের calculator বা simulator-এ মিলিয়ে নিন।

  1. Cycle time-এর numerical: একটি single-cycle RISC-V CPU-তে delay: PC-র clock-to-Q ৩০ ps, instruction memory ২৫০, register file পড়া ১৫০, ImmGen ৩৫, প্রতিটি mux ২৫, ALU ২০০, data memory ২৫০, register file setup ২০, PC setup ২০, adder ১৫০ ps. (ক) R-type, lw, sw, beq-এর সময় বের করুন। (খ) clock period আর frequency কত? (গ) mix ২০% lw, ১০% sw, ৫০% R-type, ২০% beq হলে গড় দরকার আর আদর্শ পরিবর্তনশীল clock-এর speedup কত?

    (ক)

    R-type = 30 + 250 + 150 + 25 (ALUSrc) + 200 + 25 (MemtoReg) + 20      = 700 ps
    lw     = 30 + 250 + max(150, 35 + 25) + 200 + 250 + 25 + 20          = 925 ps
    sw     = 30 + 250 + max(150, 60) + 200 + 250                          = 880 ps
    beq    = 30 + 250 + max(150 + 25 + 200, 35 + 150) + 25 (PCSrc) + 20   = 700 ps
    PC+4   = 30 + 150 + 25 + 20                                           = 225 ps

    (খ) Tc = সর্বোচ্চ = ৯২৫ ps (lw), f = ১ / ৯২৫ ps ≈ ১.০৮১ GHz.

    (গ) গড় = ০.২০ × ৯২৫ + ০.১০ × ৮৮০ + ০.৫০ × ৭০০ + ০.২০ × ৭০০ = ১৮৫ + ৮৮ + ৩৫০ + ১৪০ = ৭৬৩ ps. Speedup = ৯২৫ / ৭৬৩ ≈ ১.২১২ গুণ; গড় অপচয় ১৬২ ps (প্রায় ১৭.৫%). যাচাই: উপরের calculator-এ এই delay আর mix বসান।

  2. কোন unit, কোন সংকেত: (ক) sw x6, -8(x2) (word 0xFE612C23), x2 = 0x7FF0, x6 = ৯৯ — কোন unit কাজ করে, সাতটি control সংকেতের মান কী, ALU-র ফল আর মেমরিতে কী লেখা হয়? (খ) addi x5, x0, -1-এর ক্ষেত্রে একই প্রশ্ন।

    (ক) ব্যবহৃত: IMem, register file (rs1 = x2, rs2 = x6 পড়া), ImmGen (S-format: inst[31:25] = 1111111, inst[11:7] = 11000 → 1111 1111 1000 = −৮), ALU (যোগ), data memory (লেখা)। অব্যবহৃত: register-এ লেখা, target adder-এর ফল। সংকেত: ALUSrc = 1, MemtoReg = X, RegWrite = 0, MemRead = 0, MemWrite = 1, Branch = 0, ALUOp = 00. ALU = 0x7FF0 − 8 = 0x7FE8; ধারে Mem[0x7FE8] ← ৯৯. PC ← PC + 4.

    (খ) ব্যবহৃত: IMem, register file (rs1 = x0 → 0), ImmGen (I: 0xFFF → 0xFFFFFFFF = −১), ALU (যোগ), register-এ লেখা। Data memory অব্যবহৃত। সংকেত: ALUSrc = 1, MemtoReg = 0, RegWrite = 1, MemRead = 0, MemWrite = 0, Branch = 0, ALUOp = 00. ALU = ০ + (−১) = −১, ধারে x5 ← 0xFFFFFFFF. এটিই li x5, -1 pseudo-instruction.

  3. আটকে যাওয়া control line: একটি chip-এ উৎপাদন-ত্রুটির কারণে একটি control তার স্থায়ীভাবে একটি মানে আটকে আছে (stuck-at fault). প্রতিটি ক্ষেত্রে কোন instruction ভুল কাজ করবে, আর কী ভুল হবে? (ক) RegWrite stuck-at-0 (খ) ALUSrc stuck-at-0 (গ) MemtoReg stuck-at-0 (ঘ) Branch stuck-at-1. উদাহরণ হিসেবে x10 = 0x1000, Mem[0x1008] = ৪২, x8 = ৩৪ ধরুন।

    (ক) RegWrite = 0 সবসময়: কোনো register-এ কখনো লেখা হয় না। R-type, addi, lw — সব ভুল (ফল হারায়)। sw আর beq ঠিক থাকে, কারণ ওদের RegWrite এমনিতেই 0. প্রোগ্রাম চলবে, কিন্তু register কখনো বদলাবে না।

    (খ) ALUSrc = 0 সবসময়: ALU-র B-তে সবসময় RD2. R-type আর beq ঠিক। lw x5, 8(x10)-এ বিট ২৪..২০ = ৮, তাই B = x8 = ৩৪: ঠিকানা 0x1000 + 34 = 0x1022 — ভুল ঠিকানা (আর ৪-এর গুণিতকও নয়)। sw-ও ভুল ঠিকানায় লেখে, addi imm-এর বদলে একটি register যোগ করে।

    (গ) MemtoReg = 0 সবসময়: শুধু lw ভুল: x5-এ যায় ALU-র ফল, অর্থাৎ ঠিকানা 0x1008 (= ৪১০৪), ৪২ নয়। বাকি সব ঠিক — R-type আর addi-এর MemtoReg এমনিতেই 0, আর sw/beq-এ X.

    (ঘ) Branch = 1 সবসময়: যে instruction-এর ALU-ফল 0, সে-ই লাফ দেবে PC + (ImmGen আর Shift left 1-এর আউটপুট)-এ। যেমন sub x9, x5, x5 (ফল 0) হঠাৎ কোথাও লাফাবে; add-এর ফল 0 হলেও। ফল শূন্য না হলে কিছু হয় না, তাই ত্রুটিটি মাঝে মাঝে দেখা যায় — এমন ত্রুটি খোঁজা সবচেয়ে কঠিন। এজন্য chip পরীক্ষায় প্রতিটি control line-কে 0 আর 1 দুই মানে চালানোর test vector বানানো হয়।

  4. আউটপুট অনুমান করুন: x10 = 0x1000, Mem[0x1000] = ৩, PC = 0x00. প্রোগ্রাম:
    0x00  lw   x5, 0(x10)
    0x04  addi x6, x0, 0
    0x08  beq  x5, x0, 16      # -> 0x18
    0x0C  add  x6, x6, x5
    0x10  addi x5, x5, -1
    0x14  beq  x0, x0, -12     # -> 0x08
    0x18  sw   x6, 4(x10)
    (ক) শেষে x6 আর Mem[0x1004] কত? (খ) মোট কয়টি clock cycle লাগে, কয়টিতে PCSrc = 1? (গ) Tc = ৬০০ ps-এ মোট সময়, আর প্রতিটি instruction তার দরকারমতো সময় পেলে (অংশ ৭-এর সেট A) কত?

    (ক) লুপ x5 = ৩, ২, ১ নিয়ে চলে: x6 = ৩ + ২ + ১ = ৬, তারপর x5 = 0 হলে বেরিয়ে Mem[0x1004] ← ৬.

    (খ) lw + addi = ২; প্রতিটি লুপ-পাক ৪টি (beq না-নেওয়া, add, addi, beq নেওয়া) × ৩ = ১২; শেষে beq (নেওয়া) + sw = ২. মোট ১৬টি instruction = ১৬ cycle (CPI = ১)। PCSrc = 1 হয় ৪ বার: পেছনে ৩ লাফ + বেরোনোর ১ লাফ.

    (গ) ১৬ × ৬০০ = ৯৬০০ ps = ৯.৬ ns. দরকারমতো: lw ১টি × ৬০০ + sw ১টি × ৫৫০ + ALU-শ্রেণি (৪টি addi + ৩টি add) ৭ × ৪০০ + beq ৭ × ৩৫০ = ৬০০ + ৫৫০ + ২৮০০ + ২৪৫০ = ৬৪০০ ps — এক-তৃতীয়াংশ সময় অপচয়।

    নিজে যাচাই: simulator-এর prog-এ বসান 0x00052283, 0x00000313, 0x00028863, 0x00530333, 0xFFF28293, 0xFE000AE3, 0x00652223, আর mem-এ { 0x1000: 3 }. আউটপুটে ১৬ জোড়া লাইন আসবে, শেষ sw-এ RD2=6 আর ALU(add)=0x1004.

নিজেকে যাচাই করুন

ভুল হলে কারণসহ ব্যাখ্যা পাবেন, আবার চেষ্টাও করতে পারবেন।

ক্রমSingle-cycle RISC-V datapath-এ lw x5, 8(x10)-এর কাজগুলো সঠিক ক্রমে সাজান (▲▼ চেপে সরান)

  1. PC-র ঠিকানা দিয়ে instruction memory থেকে word আনা
  2. Register file থেকে x10 পড়া আর ImmGen-এ offset ৮ বের করা
  3. ALU-তে x10 + ৮ যোগ করে ঠিকানা বানানো
  4. Data memory থেকে সেই ঠিকানার word পড়া
  5. MemtoReg mux পেরিয়ে মানটি register file-এর write data-তে পৌঁছানো
  6. Clock-এর ধারে x5 আর PC-তে নতুন মান লেখা

একটি single-cycle RISC-V CPU-তে delay: instruction memory ২০০ ps, register file পড়া ৫০ ps, ALU ১০০ ps, data memory ২০০ ps, register file-এ লেখার setup ৫০ ps; বাকি সব শূন্য। সমর্থিত instruction: add, sub, and, or, lw, sw, beq. Clock period সর্বনিম্ন কত?

  • ৪০০ ps
  • ৫৫০ ps
  • ৬০০ ps
  • ৮০০ ps

Patterson–Hennessy ধাঁচের single-cycle RISC-V datapath-এ sw চলার সময় control সংকেতের কোন সমন্বয়টি সঠিক?

  • RegWrite = 1, MemWrite = 1, ALUSrc = 1
  • RegWrite = 0, MemWrite = 1, ALUSrc = 1
  • RegWrite = 0, MemWrite = 1, ALUSrc = 0
  • RegWrite = 0, MemRead = 1, ALUSrc = 1

RISC-V single-cycle CPU-তে PC = 0x1C-এ beq x7, x8, 8 চলছে, আর x7 = x8 = ৫০. পরের PC কত?

  • 0x20
  • 0x28
  • 0x3C
  • 0x24

Single-cycle datapath-এ instruction memory আর data memory আলাদা রাখা হয় কেন?

  • একটি cycle-এ lw-কে দুবার মেমরি ছুঁতে হয়, আর প্রতিটি unit এক cycle-এ একবারই ব্যবহার করা যায়
  • যাতে প্রোগ্রাম নিজের কোড বদলাতে না পারে
  • Instruction ৩২ বিট আর ডেটা ৬৪ বিট বলে
  • RISC-V ISA দুটি ঠিকানা-জগৎ দাবি করে বলে

একটি single-cycle RISC-V CPU-তে ALUSrc control তার স্থায়ীভাবে 0-তে আটকে গেছে। কোন instruction ভুল ফল দেবে?

  • add x7, x5, x6
  • beq x7, x8, 8
  • lw x5, 8(x10)
  • sub x8, x5, x6

Patterson–Hennessy-এর RISC-V single-cycle datapath-এ branch-এর পথে “Shift left 1” বাক্স থাকে কেন?

  • B-format-এ offset-এর imm[12:1] থাকে, তাই byte-এ নিতে ২ দিয়ে গুণ করতে হয়
  • Offset word-এ গোনা হয় বলে
  • PC + 4 বানানোর জন্য
  • Zero flag বানানোর জন্য

শব্দকোষ

লেখার মধ্যে ডটেড দাগ দেওয়া শব্দে মাউস রাখুন বা চাপুন — অর্থ দেখাবে। এই পর্বের সব নতুন শব্দ এখানে একসাথে:

মনে রাখুন

  1. উপাদান: PC, instruction memory, PC + 4 adder, register file (২ read, ১ write, x0 = 0), ImmGen, ALU (Zero-সহ), data memory, target adder; state element লেখে শুধু clock-এর ধারে।
  2. তিন mux: ALUSrc (RD2 না imm), MemtoReg (ALU না মেমরি), PCSrc (PC + 4 না target); PCSrc = Branch AND Zero.
  3. Control: R-type 0 0 1 0 0 0 10, lw 1 1 1 1 0 0 00, sw 1 X 0 0 1 0 00, beq 0 X 0 0 0 1 01 (ALUSrc, MemtoReg, RegWrite, MemRead, MemWrite, Branch, ALUOp).
  4. Timing: Tc = সবচেয়ে দীর্ঘ পথ = lw; পাঠ্যবই-সংখ্যায় ৬০০ ps, R-type ৪০০, sw ৫৫০, beq ৩৫০; mix-এ গড় অপচয় প্রায় এক-চতুর্থাংশ।
  5. বাড়ানো: addi শুধু control-এর একটি সারি; jal চায় J-ImmGen, লেখার মানে PC + 4, আর Jump সংকেত — CPI = ১ থাকে, কিন্তু Tc লম্বাই থাকে।

পরের পর্বে: এই পর্বে control-এর মানগুলো টেবিল থেকে পড়েছেন; এবার সেই টেবিল opcode থেকে gate-এ বানানো — main control-এর truth table, ALU control-এর দুই স্তর, আর hardwired control-এর নকশা।

আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ

এই পর্বটি শেয়ার করুন:
এই পর্বে ভুল পেয়েছেন? জানান