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

Hardware description language — Verilog-এর পরিচয় আর simulation

Hardware Description Languages — An Introduction to Verilog & Simulation

Taught in Bangla, this lesson explains why hardware is described in an HDL and the flow from simulation through synthesis to FPGA or chip, the concurrency mindset, Verilog modules, ports, wire, reg and logic, number literals and operators, structural adders and behavioural mux, decoder, seven-segment and parameterised ALU designs, how incomplete if and case infer latches, self-checking testbenches, simulation versus synthesis, and open-source tools like Icarus Verilog and Yosys, with a netlist simulator.

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

  • কেন hardware description language লাগে, আর লেখা থেকে FPGA বা chip পর্যন্ত পুরো design flow
  • Verilog-এর মৌলিক গঠন — module, port, wire/reg/logic, vector, সংখ্যা লেখার নিয়ম আর operator
  • Structural আর behavioural modelling — adder, mux, decoder, 7-segment decoder আর একটি parameter-ওয়ালা ALU
  • Combinational block-এ latch কেন তৈরি হয় আর কীভাবে এড়াবেন
  • Self-checking testbench লেখা, simulation বনাম synthesis-এর পার্থক্য, open-source tool আর FPGA-র ভেতরের LUT

১ · ছবি আঁকা থেকে কোড লেখা — কেন hardware description language

মডিউল ২-এর আগের পর্বগুলোতে আপনি circuit দেখেছেন ছবিতে: gate-এর চিহ্ন, তার দিয়ে জোড়া — যাকে বলে schematic. পর্ব ১১-এ full adder, পর্ব ১২-এ multiplexer আর decoder, পর্ব ১৩-এ ALU. কয়েকটি gate-এর জন্য ছবি চমৎকার। কিন্তু আজকের একটি ফোনের প্রসেসরে কয়েকশো কোটি transistor. প্রতি সেকেন্ডে একটি করে gate আঁকলেও একজন মানুষের সারাজীবনে একটি chip আঁকা শেষ হবে না — আর একটি ভুল তার খুঁজে বের করা প্রায় অসম্ভব।

ঢাকায় একটি বিশতলা ভবন বানানোর কথা ভাবুন। স্থপতি প্রতিটি ইটের ছবি আঁকেন না। তিনি লেখেন: “প্রতিটি তলায় চারটি ফ্ল্যাট, প্রতিটি ফ্ল্যাটে তিনটি ঘর, এই মাপের জানালা।” একটি ফ্ল্যাটের নকশা একবার ঠিক করলে সেটি কুড়িবার ব্যবহার হয়। ঠিকাদার সেই বর্ণনা থেকে ইট, রড আর সিমেন্টের হিসাব বের করেন। Hardware description language (HDL)-ও তাই: আপনি লেখেন “এই module-এ চারটি full adder, প্রতিটির carry পরেরটিতে যায়”, আর একটি tool সেখান থেকে gate আর তারের পুরো তালিকা বানিয়ে দেয়।

HDL দেখতে প্রোগ্রামিং ভাষার মতো — if, case, for সবই আছে — কিন্তু এটি CPU-তে চালানোর জন্য instruction নয়। এটি একটি circuit-এর বর্ণনা। C প্রোগ্রাম compile হয়ে machine code হয়; Verilog কোড “compile” হয়ে হয় gate আর তারের তালিকা, যাকে বলে netlist. একই লেখা দুইভাবে ব্যবহার হয়:

Simulation

একটি software (simulator) আপনার বর্ণনা পড়ে হিসাব করে — এই input দিলে কোন তারে কোন মান আসবে, কোন সময়ে। চিপ বানানোর আগেই ভুল ধরা পড়ে। এই কাজকে বলে simulation.

যাচাইয়ের জন্য

Synthesis

আরেকটি tool একই বর্ণনা থেকে আসল gate, flip-flop আর সংযোগ বের করে। এই রূপান্তরকে বলে logic synthesis. ফলটি যায় FPGA-তে বা কারখানায় chip বানাতে।

বানানোর জন্য

এই দুই কাজের মাঝখানে পুরো পথটিকে বলে design flow. নিচের ডায়াগ্রামে “পরে” চাপুন — একটি ছোট half adder লেখা থেকে চিপ পর্যন্ত প্রতিটি ধাপে কী তৈরি হয়, সেটি দেখুন:

  1. বর্ণনা লেখাhalf_adder.vRTL
  2. Simulatetestbenchঠিক আছে তো?
  3. Synthesisnetlistgate-এর তালিকা
  4. FPGAbitstreamমিনিটে তৈরি
  5. ASICGDSIIমাসে তৈরি
half_adder.vনকশাকারী যা লেখেন
module half_adder (input wire a, input wire b,
                   output wire sum, output wire carry);
    assign sum   = a ^ b;   // XOR gate
    assign carry = a & b;   // AND gate
endmodule
Simulator-এর আউটপুটউদাহরণ — চারটি input-ই চালানো হলো
a b | sum carry
0 0 |  0    0
0 1 |  1    0
1 0 |  1    0
1 1 |  0    1
4/4 pass
Synthesis-এর ফলউদাহরণ — Yosys-ধাঁচের সংক্ষেপিত সারাংশ
$ yosys -p "synth -top half_adder; stat" half_adder.v
   ...
   Number of cells:      2
     $_AND_              1
     $_XOR_              1
FPGA-তে বসানোউদাহরণ — LUT-এ truth table
LUT 1 (sum)  : input a,b → মেমরি-বিট 0 1 1 0
LUT 2 (carry): input a,b → মেমরি-বিট 0 0 0 1
routing      : a, b pin → দুই LUT → output pin
সব মিলিয়ে একটি bitstream ফাইল → board-এ load
ASICউদাহরণ — standard cell দিয়ে layout
XOR2 cell + AND2 cell  (কারখানার cell library থেকে)
→ placement (কোথায় বসবে) → routing (ধাতুর তার)
→ timing ও নিয়ম যাচাই → GDSII ফাইল → fab-এ chip
  1. বর্ণনা লেখা। নকশাকারী Verilog-এ লেখেন half adder-এর আচরণ: sum = a ^ b, carry = a & b. এই স্তরের লেখাকে বলে RTL (register-transfer level) — কোন তারে কী মান যাবে আর কোন register কখন কী নেবে, তার বর্ণনা। কোনো transistor-এর কথা নেই।
  2. Simulate. আরেকটি Verilog ফাইল — testbench — চারটি input জোড়া (00, 01, 10, 11) একে একে দেয় আর আউটপুট মিলিয়ে দেখে। ৪/৪ মিলেছে। ভুল থাকলে এখানেই ধরা পড়া সবচেয়ে সস্তা — কোনো hardware নষ্ট হয়নি।
  3. Synthesis. একটি synthesis tool (যেমন বিনামূল্যের Yosys) লেখাটি পড়ে বলে: একটি XOR আর একটি AND gate লাগবে। ফলটি একটি netlist — gate-এর তালিকা আর কোন pin কোন তারে। বড় নকশায় tool এখানে Boolean সরলীকরণও করে (পর্ব ০৯–১০-এর K-map আর Quine-McCluskey-র কাজটি স্বয়ংক্রিয়ভাবে)।
  4. FPGA. FPGA হলো এমন chip যার ভেতরের circuit পরে বদলানো যায়। সেখানে gate নেই, আছে ছোট LUT — একটি ছোট মেমরি, যাতে truth table-টিই লেখা থাকে। sum-এর LUT-এ 0 1 1 0, carry-র LUT-এ 0 0 0 1. Tool এগুলো বসিয়ে তার জুড়ে একটি bitstream ফাইল বানায়; board-এ load করতে কয়েক সেকেন্ড, বদলাতে আবার কয়েক মিনিট।
  5. ASIC. নির্দিষ্ট কাজের জন্য স্থায়ী chip — ASIC — বানাতে হলে কারখানার নিজস্ব cell library থেকে XOR2, AND2-এর মতো তৈরি নকশা (standard cell) নেওয়া হয়, চিপের ওপর বসানো আর তার টানা হয় (place and route), তারপর timing ও নিয়ম যাচাই শেষে GDSII ফাইল কারখানায় যায়। এখানে ভুল মানে মাস আর বিপুল খরচ নষ্ট — তাই ধাপ ২-এর যাচাই বড় প্রকল্পে মোট কাজের বড় অংশ নেয়।
মূল ধারণা

একই Verilog লেখা দুই কাজে লাগে — simulation (software-এ চালিয়ে যাচাই) আর synthesis (আসল gate বানানো)। ভাষার কিছু অংশ শুধু simulation-এর জন্য (যেমন সময়ের বিলম্ব #10, পর্দায় লেখা $display); বাকিটা দুই কাজেই খাটে। এই পর্বের নকশার উদাহরণগুলো সবই synthesizable — অর্থাৎ সত্যিকারের hardware-এ রূপান্তরযোগ্য।

Verilog, VHDL আর নতুন প্রজন্মের HDL

Verilog-এর জন্ম ১৯৮৩–৮৪ সালের দিকে, Gateway Design Automation নামের একটি মার্কিন কোম্পানিতে — প্রধান নকশাকার হিসেবে Phil Moorby-র নাম পাওয়া যায়। শুরুতে এটি ছিল ঐ কোম্পানির simulator-এর নিজস্ব ভাষা। ১৯৮৯ সালের দিকে Cadence কোম্পানিটি কিনে নেয়, আর ১৯৯০ সালে ভাষাটি সবার জন্য উন্মুক্ত করা হয়। এরপর এটি IEEE মান হয়: IEEE 1364-1995, তারপর বড় হালনাগাদ 1364-2001 (এখানেই এসেছে always @(*) আর ANSI-ধাঁচের port লেখা) আর 1364-2005. একই সময়ে তৈরি হয় এর বড় সম্প্রসারণ SystemVerilog — IEEE 1800 (প্রথম সংস্করণ ২০০৫), আর ২০০৯ সালের সংস্করণে আলাদা Verilog মানটি এর ভেতরেই মিশে যায়। এরপরও IEEE 1800-এর কয়েকটি হালনাগাদ এসেছে। আজ শিল্পে “Verilog” বললে অনেক সময় SystemVerilog-এর synthesizable অংশকেই বোঝানো হয়।

অন্য বড় ভাষাটি VHDL — নামের V এসেছে VHSIC (Very High Speed Integrated Circuit) থেকে, যা ছিল ১৯৮০-র দশকে মার্কিন প্রতিরক্ষা বিভাগের একটি কর্মসূচি। উদ্দেশ্য ছিল সরবরাহকারীদের কাছ থেকে নেওয়া chip-এর নকশা একটি নির্দিষ্ট, স্পষ্ট ভাষায় নথিভুক্ত রাখা। VHDL প্রথম IEEE মান হয় ১৯৮৭ সালে — IEEE 1076 নামে। এর গঠন Ada ভাষার মতো — কড়া type-নিয়ম, বেশি শব্দ, কিন্তু ভুল কম। মোটা দাগে বলা হয়, ইউরোপ আর প্রতিরক্ষা-মহাকাশ শিল্পে VHDL বেশি প্রচলিত, আর আমেরিকা ও এশিয়ার বাণিজ্যিক chip শিল্পে Verilog/SystemVerilog — তবে এটি কেবল প্রবণতা, কঠোর নিয়ম নয়।

ভাষাধরনকোথায় বেশি দেখা যায়একটি AND gate
Verilog / SystemVerilogC-ধাঁচের গঠন, সংক্ষিপ্তবাণিজ্যিক ASIC, FPGA, verification (SystemVerilog)assign y = a & b;
VHDLAda-ধাঁচের, কড়া typeপ্রতিরক্ষা, মহাকাশ, ইউরোপের শিল্প, অনেক FPGA প্রকল্পy <= a and b;
Chisel, SpinalHDLScala ভাষার ভেতরে লেখা “hardware generator”গবেষণা, কিছু open-source RISC-V coreio.y := io.a & io.b

নতুন প্রজন্মের ভাষাগুলো একটু আলাদা ভাবনা থেকে এসেছে। Chisel তৈরি হয়েছে UC Berkeley-তে; এতে Scala প্রোগ্রাম চালালে সেটি Verilog তৈরি করে — অর্থাৎ আপনি একটি “circuit বানানোর প্রোগ্রাম” লেখেন। Berkeley-র Rocket Chip-এর মতো কয়েকটি পরিচিত RISC-V নকশা এভাবে লেখা বলে জানা যায়। SpinalHDL-ও Scala-ভিত্তিক; জনপ্রিয় open-source VexRiscv core এতে লেখা। তবে প্রায় সবই শেষে Verilog বের করে দেয়, কারণ synthesis আর FPGA tool-এর জগৎ এখনো Verilog/VHDL-কেন্দ্রিক। তাই এই কোর্সে আমরা Verilog (আর দরকারমতো SystemVerilog-এর দুই-একটি সুবিধা) শিখব।

২ · সবচেয়ে বড় মানসিক বদল — hardware একসাথে চলে

C বা Python শেখা কারও কাছে প্রথম Verilog ফাইলটি পরিচিত মনে হয়: লাইনের পর লাইন, সেমিকোলন, = চিহ্ন। আর সেখানেই ফাঁদ। C-তে লাইনগুলো একটির পর একটি চলে — CPU একবারে একটি instruction. কিন্তু circuit-এ প্রতিটি gate নিজের জায়গায় বসে আছে, বিদ্যুৎ পাচ্ছে, আর input বদলালেই নিজের output বদলাচ্ছে — অন্য gate-এর “পালা” আসার অপেক্ষা ছাড়াই। একে বলে concurrency.

একটি বাড়ির সুইচবোর্ডের কথা ভাবুন। বসার ঘরের বাতি, রান্নাঘরের ফ্যান, বারান্দার বাতি — প্রতিটি নিজের তারে জোড়া। আপনি রান্নাঘরের সুইচ চাপলে ফ্যান সঙ্গে সঙ্গে ঘোরে; বসার ঘরের বাতি “আগে জ্বলবে, তারপর ফ্যান” — এমন কোনো ক্রম নেই। সুইচবোর্ডের নকশা-কাগজে কোন লাইনটি আগে লেখা, তাতে কিছু আসে যায় না। Verilog-এর প্রতিটি assign এমনই একটি স্থায়ী তার।

নিচের তিনটি লাইন একটি ছোট circuit বর্ণনা করে: y = (a AND b) OR (NOT c). ইচ্ছে করে লাইনগুলো “উল্টো” ক্রমে লেখা — আগে y, পরে তার input n1 আর n2. Verilog-এ এতে কোনো সমস্যা নেই। একই তিনটি লাইন C-তে লিখলে কী হয়, সেটিও পাশাপাশি দেখুন। শুরুতে a = 1, b = 0, c = 1; তারপর b বদলে 1 হয়:

1// Verilog: তিনটি স্থায়ী তার2assign y  = n1 | n2;3assign n1 = a & b;4assign n2 = ~c;5// C: একই লাইন, একবার, ক্রমানুসারে6y  = n1 | n2;7n1 = a & b;8n2 = !c;
মান (a = 1, c = 1 স্থির)
b
Verilog n1
Verilog n2
Verilog y
C n1
C y
  1. স্থির অবস্থা। a = 1, b = 0, c = 1. তিনটি assign-ই “সবসময় সত্য”: n1 = a·b = 0, n2 = ¬c = 0, y = n1 ∨ n2 = 0. Verilog-এর তিনটি লাইন একসাথে আলো পাচ্ছে — কারণ তিনটিই সারাক্ষণ সক্রিয়। C-এর দিকেও শুরুর মান একই।
  2. b বদলাল 0 থেকে 1. Simulator দেখে, কোন কোন assign-এর ডান পাশে b আছে — শুধু লাইন ৩. সেটি নতুন করে হিসাব হয়: n1 = 1·1 = 1. লাইনের ক্রম এখানে কোনো ভূমিকা রাখেনি; ভূমিকা রেখেছে “কে কোন তার পড়ে”। y এই মুহূর্তে এখনো পুরোনো 0.
  3. n1 বদলাল, তাই y জাগল। n1 পড়ে লাইন ২, তাই সেটি আবার হিসাব হয়: y = 1 ∨ 0 = 1. লাইন ২ ফাইলের ওপরে লেখা, তবু ঠিক সময়ে হিসাব হলো। একে বলে event-driven simulation — একটি পরিবর্তন (event) কেবল তার ওপর নির্ভরশীল অংশগুলোকে জাগায়।
  4. Circuit স্থির। y বদলালেও কেউ y পড়ে না, তাই নতুন কোনো ঘটনা নেই। লাইন ৪ (n2) একবারও জাগেনি, কারণ c বদলায়নি। বাস্তব chip-এ এই পুরো ঢেউ কয়েক picosecond-এ শেষ হয় — প্রতিটি gate-এর নিজস্ব delay আছে (পর্ব ১৮-এ timing). Simulator-এ এগুলো “একই মুহূর্তের” ভেতরে ছোট ছোট উপ-ধাপে (delta cycle) হিসাব হয়।
  5. এবার C. b = 1 হওয়ার পর একই তিনটি লাইন C-তে একবার, ওপর থেকে নিচে চলে। লাইন ৬ প্রথমে চলে, তখনো n1-এ পুরোনো 0, তাই y = 0 ∨ 0 = 0.
  6. C-এর ফল ভুল। লাইন ৭–৮ চলার পর n1 = 1, কিন্তু y আর কেউ হিসাব করে না — y = 0, অথচ সঠিক উত্তর 1. C-তে লাইনের ক্রমই অর্থ ঠিক করে; Verilog-এর assign-এ ক্রম অর্থহীন। Verilog-কে C ভেবে পড়া — নতুনদের সবচেয়ে প্রচলিত ভুল এটিই।

সাধারণ ভুল: “প্রথমে n1 হিসাব করি, তারপর y” — এভাবে ভেবে লাইন সাজানো। Verilog-এ assign কোনো কাজ নয় যা একবার ঘটে; এটি একটি স্থায়ী সম্পর্ক — “y সবসময় n1 OR n2-এর সমান”। আরেকটি ভুল: একই তারে দুটি assign লেখা (assign y = a; assign y = b;). এর মানে দুটি gate-এর output একসাথে জোড়া — বাস্তবে short circuit, আর simulator-এ মান হয় x (অজানা) যখন দুটি ভিন্ন মান চালায়।

Verilog-এ ক্রমিক লেখাও আছে — always block-এর ভেতরে (অংশ ৬)। কিন্তু সেখানেও পুরো block-টি একটি circuit-এর বর্ণনা, আর প্রতিটি block অন্য সব block ও assign-এর সাথে একসাথে চলে।

৩ · Verilog-এর মূল গঠন — module, port, wire, reg আর সংখ্যা

Verilog-এ প্রতিটি circuit একটি module. Module হলো একটি বাক্সের মতো — বাইরে থেকে দেখা যায় শুধু তার pin (input আর output), ভেতরে কী আছে তা বাক্সের নিজস্ব ব্যাপার। একটি module অন্য module-কে নিজের ভেতরে বসাতে পারে, ঠিক যেমন একটি বড় circuit board-এ ছোট ছোট chip বসানো থাকে। নিচের ছোট module-টি একটি ৪-বিট সংখ্যা নিয়ে বলে সেটি বিজোড় কি না আর শূন্য কি না:

// A 4-bit number n: is it odd? is it zero?
module nibble_info (
    input  wire [3:0] n,        // 4-bit input bus: n[3] = MSB, n[0] = LSB
    output wire       is_odd,
    output wire       is_zero
);
    assign is_odd  = n[0];                // LSB = 1 means odd
    assign is_zero = (n == 4'b0000);      // a 4-input NOR in hardware
endmodule

লাইন ধরে ধরে পড়া যাক:

  • module nibble_info ( … ); থেকে endmodule — একটি module-এর শুরু আর শেষ। নামটি দিয়েই পরে অন্য জায়গায় একে বসানো হবে।
  • Port — বন্ধনীর ভেতরের তালিকা। input মানে বাইরে থেকে আসে, output মানে বাইরে যায় (দুই দিকেই চলে এমন inout-ও আছে, যেমন চিপের data pin). Verilog-2001 থেকে port-এর দিক আর type এক জায়গায় লেখা যায়, যেমনটি এখানে।
  • [3:0] — একটি vector বা bus: চারটি তার একসাথে, n[3] থেকে n[0]. প্রচলিত রীতি হলো বাঁ দিকের সংখ্যাটি MSB — [7:0] মানে ৮ বিট। একটি অংশ আলাদা করে নেওয়া যায়: n[3:2] মানে ওপরের দুই বিট।
  • assign — continuous assignment: বাঁ পাশের তার সবসময় ডান পাশের মানের সমান থাকে। অংশ ২-এ দেখেছেন, এটি একবারের কাজ নয়, একটি স্থায়ী সংযোগ।
  • // আর /* … */ — মন্তব্য, C-এর মতোই। প্রতিটি statement শেষ হয় ; দিয়ে।

wire, reg আর logic — নামের বিভ্রান্তি

Verilog-এ সংকেত রাখার দুই প্রধান ধরন আছে, আর এদের নাম নতুনদের সবচেয়ে বেশি বিভ্রান্ত করে:

Typeকে মান দেয়মনে রাখার উপায়ভুল ধারণা
wireassign, বা কোনো module-এর outputএকটি তার — নিজে কিছু মনে রাখে না, যে চালায় তার মান বহন করে—
regalways বা initial block-এর ভেতরের = বা <=procedural block-এ মান পাওয়া একটি variable“reg মানে register/flip-flop” — না! combinational always @(*)-এ reg হয় সাধারণ gate-এর output
logic (SystemVerilog)দুটোর যেকোনোটি (একটিমাত্র চালক থাকলে)wire/reg-এর ঝামেলা এড়াতে নতুন সাধারণ typeএকাধিক চালক লাগলে (যেমন bus-এর z) তবুও wire লাগে

অর্থাৎ reg শব্দটি কেবল বলে “এই সংকেত procedural block থেকে মান পায়” — hardware-এ সেখানে flip-flop বসবে কি না, তা ঠিক হয় block-টি কেমন লেখা হয়েছে তা দেখে: clock-এর ধারে মান নিলে flip-flop, সব input-এর ওপর সঙ্গে সঙ্গে নির্ভর করলে সাধারণ gate. SystemVerilog-এর logic এই বিভ্রান্তি দূর করতে এসেছে, আর নতুন প্রকল্পে প্রায়ই সব জায়গায় logic লেখা হয়।

সংখ্যা লেখার নিয়ম আর চার রকম মান

Hardware-এ প্রতিটি সংকেতের নির্দিষ্ট বিট-সংখ্যা আছে, তাই Verilog-এ সংখ্যার সাথে তার মাপও লেখা হয়: <বিট>'<ভিত্তি><অঙ্ক>. ভিত্তি b (binary), o (octal), d (decimal), h (hex) — পর্ব ০৪-এর সংখ্যা পদ্ধতি এখানে সরাসরি কাজে লাগে।

লেখাবিটbinary-তেমানে
4'b1010৪1010দশমিকে ১০
8'hFF৮1111 1111দশমিকে ২৫৫
8'd200৮1100 1000দশমিকে লেখা ২০০
3'o7৩111octal ৭
8'b1010_0110৮1010 0110_ কেবল পড়ার সুবিধার জন্য, মানে বদলায় না
4'b10x1৪1 0 x 1x = অজানা মান
1'bz১zz = high impedance — কেউ চালাচ্ছে না, তার “ভাসছে”
10অন্তত ৩২…0000 1010মাপ না লিখলে দশমিক, কমপক্ষে ৩২ বিট — বিপদ এড়াতে মাপ লিখুন

খেয়াল করুন, Verilog-এর একটি বিট শুধু 0 আর 1 নয় — চার রকম মান নিতে পারে: 0, 1, x (অজানা — যেমন শুরুতে কেউ মান দেয়নি, বা দুটি চালক দুই মান দিচ্ছে) আর z (high impedance, তার কারও সাথে জোড়া নেই)। Simulation-এ x দেখা গেলে প্রায় সবসময় বুঝবেন কোথাও কিছু জোড়া হয়নি বা শুরুর মান দেওয়া হয়নি — এটি ভুল খোঁজার দারুণ সূত্র। বাস্তব hardware-এ অবশ্য প্রতিটি তারে 0 বা 1-ই থাকে; x কেবল simulator-এর “আমি জানি না” বলার উপায়।

৪ · Operator আর continuous assignment — তারের ওপর হিসাব

Verilog-এর অনেক operator C-এর মতো দেখতে, কিন্তু কয়েকটি একেবারেই hardware-এর নিজস্ব। নিচের টেবিলে প্রতিটির ফল হাতে হিসাব করা — ধরে নিন a = 4'b1010 (১০) আর b = 4'b0110 (৬)। Bitwise operator প্রতিটি বিট-অবস্থানে আলাদা gate বসায় (৪ বিটের AND মানে পাশাপাশি চারটি AND gate):

ধরনলেখাফলHardware-এ যা হয়
Bitwise ANDa & b4'b0010৪টি AND gate, বিট-বিট
Bitwise ORa | b4'b1110৪টি OR gate
Bitwise XORa ^ b4'b1100৪টি XOR gate
Bitwise NOT~a4'b0101৪টি inverter
Reduction AND&a1'b0সব বিট মিলিয়ে একটি ৪-input AND — “সব 1 কি?”
Reduction OR|a1'b1“কোনো একটি 1 আছে কি?”
Reduction XOR^a1'b0দুটি 1 (জোড়) → 0; এটিই parity bit
Concatenation{a, b}8'b1010_0110কোনো gate নেই — শুধু তারগুলো পাশাপাশি সাজানো
Replication{2{a}}8'b1010_1010একই তার দুবার
Sign extension{{4{a[3]}}, a}8'b1111_1010MSB-র কপি — ৪-বিটের −৬ থেকে ৮-বিটের −৬
Conditional(a > b) ? a : b4'b1010একটি comparator আর একটি 2:1 multiplexer
যোগ (৪-বিট ফল)a + b4'b0000১০ + ৬ = ১৬, কিন্তু ৪ বিটে শুধু 0000 থাকে — carry হারাল
যোগ (৫-বিট ফল){c, s} = a + b5'b1_0000বাঁ পাশ ৫ বিট হলে carry-ও ধরা পড়ে
Shifta << 1 / a >> 14'b0100 / 4'b0101শুধু তার এক ঘর সরিয়ে জোড়া — কোনো gate নেই

তিনটি বিষয় এখানে বিশেষভাবে লক্ষ করার মতো। প্রথমত, reduction operator (&a, |a, ^a) C-তে নেই; একটিমাত্র operand-এর সব বিট মিলিয়ে এক বিট ফল দেয়। দ্বিতীয়ত, concatenation আর shift-এর জন্য কোনো gate লাগে না — hardware-এ এগুলো কেবল তার কোথায় জুড়বে তার নির্দেশ, তাই এগুলো “বিনামূল্যে”। তৃতীয়ত, ফলের মাপ বাঁ পাশ ঠিক করে: a + b-এর ফল ৪-বিট তারে রাখলে উপচে পড়া carry চুপচাপ হারিয়ে যায় — পর্ব ০৫-এর overflow, এবার hardware-এর ভাষায়। কোনো error আসে না; কিছু tool কেবল সতর্কবার্তা দেয়।

Logical operator (!, &&, ||) আর তুলনা (==, !=, <, >=) C-এর মতোই এক বিটের সত্য/মিথ্যা দেয়। আর একটি assign-এ conditional operator দিয়েই একটি 2:1 multiplexer লেখা যায়:

module mux2 #(parameter W = 8) (
    input  wire [W-1:0] d0, d1,
    input  wire         sel,
    output wire [W-1:0] y,
    output wire         same
);
    assign y    = sel ? d1 : d0;    // W-bit 2:1 multiplexer
    assign same = (d0 == d1);       // W-bit equality comparator
endmodule

এখানে #(parameter W = 8) একটি parameter — module-এর মাপ একটি নামে রাখা। একই module ৮-বিট, ১৬-বিট বা ৩২-বিট mux হিসেবে বসানো যায়, শুধু বসানোর সময় mux2 #(.W(32)) m (...) লিখে। Parameter synthesis-এর আগেই একটি স্থির সংখ্যা হয়ে যায়; চলার সময় এটি বদলায় না, কারণ চলার সময় hardware-এর তার বাড়ে-কমে না।

৫ · Structural modelling — ছোট module জুড়ে বড় module

পর্ব ১১-এ দেখেছেন, একটি full adder দুটি half adder আর একটি OR gate দিয়ে বানানো যায়, আর চারটি full adder-এর carry পরপর জুড়লে হয় একটি ৪-বিট ripple-carry adder. ঠিক এই ছবিটিকেই Verilog-এ লেখা যায় — একে বলে structural modelling: কোন ছোট module কতগুলো বসবে, আর কোন তার কোথায় জুড়বে। একটি module-কে আরেকটির ভেতরে বসানোকে বলে instantiation, আর বসানো প্রতিটি কপি একটি instance.

module half_adder (
    input  wire a, b,
    output wire sum, carry
);
    assign sum   = a ^ b;
    assign carry = a & b;
endmodule

module full_adder (
    input  wire a, b, cin,
    output wire sum, cout
);
    wire s1, c1, c2;                 // internal wires between the two half adders
    half_adder ha1 (.a(a),  .b(b),   .sum(s1),  .carry(c1));
    half_adder ha2 (.a(s1), .b(cin), .sum(sum), .carry(c2));
    assign cout = c1 | c2;
endmodule

half_adder ha1 ( … ); লাইনের মানে: “half_adder ধরনের একটি instance বসবে, যার নাম ha1”. বন্ধনীর ভেতরে .a(a) লেখার অর্থ — instance-এর a pin জোড়া হবে এই module-এর a তারে। একে বলে named port connection. ক্রম অনুযায়ী (half_adder ha1 (a, b, s1, c1);) লেখাও বৈধ, কিন্তু port-এর ক্রম ভুল হলে নীরবে ভুল circuit তৈরি হয় — তাই বড় নকশায় নাম দিয়ে জোড়াই নিরাপদ রীতি। s1, c1, c2 ভেতরের তার, বাইরে থেকে দেখা যায় না।

module adder4 (
    input  wire [3:0] a, b,
    input  wire       cin,
    output wire [3:0] sum,
    output wire       cout
);
    wire c1, c2, c3;                 // carries between the stages
    full_adder fa0 (.a(a[0]), .b(b[0]), .cin(cin), .sum(sum[0]), .cout(c1));
    full_adder fa1 (.a(a[1]), .b(b[1]), .cin(c1),  .sum(sum[1]), .cout(c2));
    full_adder fa2 (.a(a[2]), .b(b[2]), .cin(c2),  .sum(sum[2]), .cout(c3));
    full_adder fa3 (.a(a[3]), .b(b[3]), .cin(c3),  .sum(sum[3]), .cout(cout));
endmodule

এবার একটি নির্দিষ্ট যোগে প্রতিটি instance-এর তারে কী মান আসে, দেখা যাক: a = 4'b1011 (১১), b = 4'b0110 (৬), cin = 0. Hardware-এ চারটি instance একসাথে বসে আছে; কিন্তু fa1 তার সঠিক উত্তর দিতে পারে কেবল fa0-এর carry পৌঁছানোর পরে — তাই মান “ঢেউয়ের মতো” ডানে থেকে বাঁয়ে যায়:

1wire c1, c2, c3;2full_adder fa0 (a[0], b[0], cin → sum[0], c1);3full_adder fa1 (a[1], b[1], c1  → sum[1], c2);4full_adder fa2 (a[2], b[2], c2  → sum[2], c3);5full_adder fa3 (a[3], b[3], c3  → sum[3], cout);6// ফল: {cout, sum}
প্রতিটি instance: (a, b, cin) → sum, cout
fa0
fa1
fa2
fa3
sum[3:0]
cout
  1. Input বসল। a = 1011, b = 0110, cin = 0. প্রতিটি instance তার নিজের a আর b বিট সঙ্গে সঙ্গে পেয়ে গেছে: fa0 পায় (1, 0), fa1 পায় (1, 1), fa2 পায় (0, 1), fa3 পায় (1, 0). কিন্তু fa1–fa3-এর cin এখনো অজানা (?), কারণ সেটি আসবে পাশের instance থেকে।
  2. fa0. (1, 0, 0): ভেতরের ha1 দেয় s1 = 1 ⊕ 0 = 1, c1 = 1·0 = 0; ha2 দেয় sum = 1 ⊕ 0 = 1, c2 = 0; cout = 0 ∨ 0 = 0. তাই sum[0] = 1, আর তার c1 = 0 যায় fa1-এ।
  3. fa1. (1, 1, 0): ১ + ১ + ০ = binary 10, তাই sum[1] = 0, cout = 1. এই carry (c2 = 1) যায় fa2-এ।
  4. fa2. (0, 1, 1): ০ + ১ + ১ = 10, তাই sum[2] = 0, c3 = 1.
  5. fa3. (1, 0, 1): ১ + ০ + ১ = 10, তাই sum[3] = 0, cout = 1. carry চারটি ধাপ পেরিয়ে শেষ মাথায় পৌঁছাল — এই অপেক্ষাই ripple-carry adder-এর ধীরতার কারণ (পর্ব ১১-এর carry-lookahead এর সমাধান)।
  6. ফল। {cout, sum} = 1 0001 = ১৬ + ১ = ১৭ = ১১ + ৬. মিলেছে। লক্ষ করুন, Verilog ফাইলে fa3-কে fa0-এর আগে লিখলেও ফল একই হতো — instance-এর ক্রম নয়, তারের সংযোগই ঠিক করে কে কার জন্য অপেক্ষা করবে।

এই পাতার runner-এ কোনো Verilog simulator নেই, তাই নিচের JavaScript-টি একটি খুব ছোট “Verilog-ধাঁচের” simulator — আসল Verilog নয়। এতে half_adder আর full_adder হলো function, আর adder4 একটি netlist — instance-এর নাম আর প্রতিটি pin কোন নামের তারে জোড়া, ঠিক উপরের Verilog-এর মতো। এটি event ব্যবহার করে না; instance-গুলো সংকেতের ক্রমে সাজানো বলে একবার ওপর থেকে নিচে হিসাব করলেই চলে। তারপর একটি self-checking testbench সব ২৫৬টি (a, b) জোড়া চালিয়ে উত্তর মেলায়। Run চাপুন:

// Tiny "Verilog-like" netlist simulator (no events): instances + named wires, like adder4.v
const half_adder = (a, b) => ({ sum: a ^ b, carry: a & b });
function full_adder(a, b, cin) {               // ha1 + ha2 + OR, as in full_adder.v
  const ha1 = half_adder(a, b), ha2 = half_adder(ha1.sum, cin);
  return { sum: ha2.sum, cout: ha1.carry | ha2.carry };
}
const adder4 = [                               // fa0..fa3 chained by wires c1, c2, c3
  ["fa0", { a: "a0", b: "b0", cin: "cin", sum: "s0", cout: "c1" }],
  ["fa1", { a: "a1", b: "b1", cin: "c1",  sum: "s1", cout: "c2" }],
  ["fa2", { a: "a2", b: "b2", cin: "c2",  sum: "s2", cout: "c3" }],
  ["fa3", { a: "a3", b: "b3", cin: "c3",  sum: "s3", cout: "cout" }],
];
function simulate(a, b, cin, trace) {
  const w = { cin };                           // the wire table: name -> 0/1
  for (let i = 0; i < 4; i++) { w["a" + i] = (a >> i) & 1; w["b" + i] = (b >> i) & 1; }
  for (const [name, p] of adder4) {            // listed in signal order, so one pass is enough
    const r = full_adder(w[p.a], w[p.b], w[p.cin]);
    w[p.sum] = r.sum; w[p.cout] = r.cout;
    if (trace) console.log(`  ${name}: a=${w[p.a]} b=${w[p.b]} cin=${w[p.cin]} -> sum=${r.sum} cout=${r.cout}`);
  }
  return { sum: w.s0 | w.s1 << 1 | w.s2 << 2 | w.s3 << 3, cout: w.cout };
}
const bin = (v, n) => v.toString(2).padStart(n, "0");
let pass = 0, fail = 0;                        // self-checking testbench: every a, b with cin = 0
for (let a = 0; a < 16; a++) for (let b = 0; b < 16; b++) {
  const r = simulate(a, b, 0, false), got = r.cout << 4 | r.sum;
  if (got === a + b) pass++; else { fail++; console.log(`FAIL: ${a} + ${b} gave ${got}`); }
}
for (const [a, b] of [[3, 5], [9, 7], [15, 15]]) {
  const r = simulate(a, b, 0, false);
  console.log(`a=${bin(a, 4)} b=${bin(b, 4)} -> cout=${r.cout} sum=${bin(r.sum, 4)}  (${a}+${b}=${a + b})`);
}
console.log("trace for a=1011 (11), b=0110 (6), cin=0:");
simulate(0b1011, 0b0110, 0, true);
console.log(`${pass}/${pass + fail} pass, ${fail} errors`);

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

a=0011 b=0101 -> cout=0 sum=1000  (3+5=8)
a=1001 b=0111 -> cout=1 sum=0000  (9+7=16)
a=1111 b=1111 -> cout=1 sum=1110  (15+15=30)
trace for a=1011 (11), b=0110 (6), cin=0:
  fa0: a=1 b=0 cin=0 -> sum=1 cout=0
  fa1: a=1 b=1 cin=0 -> sum=0 cout=1
  fa2: a=0 b=1 cin=1 -> sum=0 cout=1
  fa3: a=1 b=0 cin=1 -> sum=0 cout=1
256/256 pass, 0 errors

প্রথম তিন লাইন $display-ধাঁচের: ৩ + ৫ = ৮ (cout = 0), ৯ + ৭ = ১৬ — ৪ বিটে sum = 0000, কিন্তু cout = 1 বলে উত্তর হারায়নি — আর ১৫ + ১৫ = ৩০ = 1 1110. তারপর trace: fa0 থেকে fa3 পর্যন্ত প্রতিটি instance-এর মান ঠিক উপরের stepper-এর মতো — sum = 1, 0, 0, 0 আর carry 0 → 1 → 1 → 1. শেষ লাইন 256/256 pass, 0 errors: সব input জোড়ায় {cout, sum} মিলেছে a + b-এর সাথে। একে বলে exhaustive testing — সম্ভাব্য সব input চালানো। ৪-বিটে ২৮ = ২৫৬টি জোড়া, মুহূর্তে শেষ। কিন্তু ৩২-বিট adder-এ ২৬৪টি জোড়া — প্রতি সেকেন্ডে শতকোটি পরীক্ষা করলেও শত শত বছর লাগবে। তাই বড় নকশায় বেছে নেওয়া (corner case) আর এলোমেলো (random) পরীক্ষা, আর গাণিতিক প্রমাণ (formal verification) লাগে। একটি ভুল ঢুকিয়ে দেখুন: full_adder-এ ha1.carry | ha2.carry-কে ha1.carry & ha2.carry করে আবার চালান — testbench সঙ্গে সঙ্গে FAIL-এর লাইন ছাপবে।

আরও ছোট করে লেখা: generate আর “+” operator

৩২-বিট adder-এ ৩২টি instance লাইন হাতে লেখা ক্লান্তিকর আর ভুলের ঝুঁকি বাড়ায়। Verilog-এর generate loop synthesis-এর আগেই লাইনগুলো “খুলে” লিখে দেয় — loop-টি hardware-এ কোনো loop নয়, N কপি instance:

module adder_n #(parameter N = 32) (
    input  wire [N-1:0] a, b,
    input  wire         cin,
    output wire [N-1:0] sum,
    output wire         cout
);
    wire [N:0] c;                    // c[0] = cin, c[N] = cout
    assign c[0] = cin;
    genvar i;
    generate
        for (i = 0; i < N; i = i + 1) begin : stage
            full_adder fa (.a(a[i]), .b(b[i]), .cin(c[i]), .sum(sum[i]), .cout(c[i+1]));
        end
    endgenerate
    assign cout = c[N];
endmodule

বাস্তবের নকশায় অবশ্য সাধারণত এক লাইনেই লেখা হয়: assign {cout, sum} = a + b + cin;. তখন synthesis tool নিজে ঠিক করে কোন ধরনের adder বসাবে — ছোট আর ধীর ripple, নাকি বড় আর দ্রুত carry-lookahead বা prefix adder — timing-এর লক্ষ্য দেখে।

৬ · Behavioural modelling — always @(*) আর case

Structural লেখায় আপনি gate বা module নিজে সাজান। কিন্তু অনেক সময় “কী করতে হবে” লেখা সহজ, “কীভাবে gate সাজাতে হবে” নয়। যেমন একটি 7-segment display-এর decoder — দশটি অঙ্কের প্রতিটিতে সাতটি বাতির কোনটি জ্বলবে। প্রতিটি বাতির জন্য K-map করে gate বের করা যায় (পর্ব ০৯), কিন্তু একটি টেবিল লিখে দিলে synthesis tool নিজেই সেই সরলীকরণ করে দেয়। এভাবে আচরণ বর্ণনা করাকে বলে behavioural modelling.

Behavioural combinational লেখার মূল হাতিয়ার always @(*) block:

  • @(*) — sensitivity list: “block-এর ভেতরে যে সংকেতগুলো পড়া হয়, তার যেকোনোটি বদলালে পুরো block আবার হিসাব হবে”। * মানে সবগুলো, tool নিজে খুঁজে নেয়। Verilog-1995-এ তালিকা হাতে লিখতে হতো (@(a or b or sel)), আর একটি নাম বাদ পড়লে simulation আর আসল hardware ভিন্ন আচরণ করত — তাই ২০০১ সালে @(*) আসে।
  • always_comb — SystemVerilog-এর সমতুল্য। এতে tool আরও কড়া পরীক্ষা করে: block-টি সত্যিই combinational কি না, আর ভুল করে latch তৈরি হচ্ছে কি না (অংশ ৭). নতুন SystemVerilog প্রকল্পে এটিই পছন্দের।
  • ভেতরে = — blocking assignment. Block জাগলে ভেতরের লাইনগুলো ওপর থেকে নিচে চলে, প্রতিটি = সঙ্গে সঙ্গে মান বসায় — এখানে C-এর মতোই। কিন্তু মনে রাখুন, পুরো block-টি শেষ পর্যন্ত একটি circuit বর্ণনা করে: input থেকে output-এর একটি স্থির সম্পর্ক। Combinational block-এ সবসময় = ব্যবহার করুন।
  • যে সংকেত always-এর ভেতরে মান পায়, তাকে reg (বা logic) হিসেবে ঘোষণা করতে হয় — অংশ ৩-এর সেই নামের বিভ্রান্তি। এখানে reg হলেও কোনো register তৈরি হয় না।

4:1 multiplexer আর 3:8 decoder

পর্ব ১২-এর দুটি পরিচিত circuit. case statement C-এর switch-এর মতো, কিন্তু break লাগে না — একটি শাখা মিললে শুধু সেটিই চলে:

module mux4 (
    input  wire [3:0] d,            // four data inputs d[0]..d[3]
    input  wire [1:0] sel,
    output reg        y
);
    always @(*) begin
        case (sel)
            2'b00:   y = d[0];
            2'b01:   y = d[1];
            2'b10:   y = d[2];
            2'b11:   y = d[3];
            default: y = 1'b0;      // only reached if sel has x/z in simulation
        endcase
    end
endmodule

module decoder3to8 (
    input  wire [2:0] a,
    input  wire       en,
    output reg  [7:0] y             // one-hot: exactly one bit is 1 when en = 1
);
    always @(*) begin
        y = 8'b0000_0000;           // default first: every path assigns y
        if (en)
            y = 8'b0000_0001 << a;  // a = 3'd5 gives 8'b0010_0000
    end
endmodule

4:1 mux-টি এক লাইনেও লেখা যায়: assign y = d[sel]; — vector-এর সূচক হিসেবে একটি সংকেত বসালেই multiplexer তৈরি হয়। Decoder-এ লক্ষ করুন y = 8'b0000_0000; লাইনটি — block-এর শুরুতে একটি default মান। এরপর en = 0 হলেও y-এর একটি নির্দিষ্ট মান থাকে। এই অভ্যাসটি অংশ ৭-এর latch-এর ফাঁদ থেকে বাঁচায়। আর 8'b0000_0001 << a: a = 5 হলে 1-কে পাঁচ ঘর বাঁয়ে সরিয়ে 0010_0000 — অর্থাৎ y[5] = 1, ঠিক decoder-এর সংজ্ঞা।

BCD থেকে 7-segment decoder

ক্যালকুলেটর বা দোকানের ডিজিটাল ঘড়ির প্রতিটি অঙ্ক সাতটি দণ্ড (a থেকে g) দিয়ে তৈরি — একে বলে 7-segment display. নিচের decoder একটি BCD অঙ্ক (০–৯) নিয়ে কোন দণ্ড জ্বলবে তা বলে। এখানে ধরা হয়েছে common-cathode display, যেখানে 1 মানে দণ্ড জ্বলবে; বিটের ক্রম {g, f, e, d, c, b, a}:

module bcd_to_7seg (
    input  wire [3:0] bcd,
    output reg  [6:0] seg           // {g,f,e,d,c,b,a}; 1 = segment on
);
    always @(*) begin
        case (bcd)
            4'd0: seg = 7'b011_1111;    // a b c d e f
            4'd1: seg = 7'b000_0110;    //   b c
            4'd2: seg = 7'b101_1011;    // a b   d e   g
            4'd3: seg = 7'b100_1111;    // a b c d     g
            4'd4: seg = 7'b110_0110;    //   b c     f g
            4'd5: seg = 7'b110_1101;    // a   c d   f g
            4'd6: seg = 7'b111_1101;    // a   c d e f g
            4'd7: seg = 7'b000_0111;    // a b c
            4'd8: seg = 7'b111_1111;    // all seven
            4'd9: seg = 7'b110_1111;    // a b c d   f g
            default: seg = 7'b000_0000; // 10..15 are not BCD: blank
        endcase
    end
endmodule

এটি আসলে একটি truth table: ৪টি input, ৭টি output, ১৬টি সারি। ১০–১৫ BCD নয়, তাই সেখানে default দিয়ে সব বন্ধ। আপনার board-এ common-anode display থাকলে সংকেত উল্টো (0 মানে জ্বলে) — তখন শুধু ~seg পাঠালেই হবে।

একটি ছোট ALU — parameter দিয়ে মাপ

পর্ব ১৩-এর ALU-র একটি ছোট সংস্করণ। op তিন বিট, তাই আটটি কাজ; মাপ W একটি parameter, ডিফল্ট ৮ বিট। এখানের op-এর সংকেত (000 = ADD …) এই উদাহরণের নিজস্ব; পর্ব ১৩ বা আসল প্রসেসরের ALU-র encoding আলাদা হতে পারে।

module alu #(parameter W = 8) (
    input  wire [W-1:0] a, b,
    input  wire [2:0]   op,
    output reg  [W-1:0] y,
    output wire         zero
);
    always @(*) begin
        case (op)
            3'b000:  y = a + b;                 // ADD (carry dropped)
            3'b001:  y = a - b;                 // SUB (wraps around)
            3'b010:  y = a & b;
            3'b011:  y = a | b;
            3'b100:  y = a ^ b;
            3'b101:  y = ~a;
            3'b110:  y = a << 1;
            3'b111:  y = (a < b) ? 1 : 0;       // unsigned set-less-than
            default: y = {W{1'b0}};
        endcase
    end
    assign zero = (y == {W{1'b0}});             // flag: result is all zeros
endmodule

Synthesis tool এখান থেকে বানাবে একটি adder, একটি subtractor (বা adder আর inverter — tool দুটিকে একত্র করতে পারে), কয়েকটি bitwise gate-এর সারি, একটি comparator, আর সবার শেষে একটি ৮-input multiplexer যা op দেখে একটি ফল বেছে নেয়। {W{1'b0}} হলো replication — W বিটের শূন্য, তাই W বদলালেও কোড ঠিক থাকে। zero flag একটি assign, যা always block-এর সাথে একসাথে চলে।

এই ALU-র প্রতিটি শাখা ঠিক কী মান দেবে, C দিয়ে একটি স্বাধীন হিসাব চালিয়ে মিলিয়ে নেওয়া যায় — hardware নকশায় এটি একটি প্রচলিত কৌশল: software-এ একটি “reference model” লিখে hardware-এর ফলের সাথে তুলনা। C-এর switch Verilog-এর case-এর মতোই, আর uint8_t-এ cast করা মানে ৮ বিটে কেটে রাখা — ঠিক W = 8-এর মতো:

#include <stdio.h>
#include <stdint.h>

/* Cross-check of alu.v with W = 8: the same case statement, written in C */
static uint8_t alu(uint8_t a, uint8_t b, unsigned op) {
    switch (op) {
    case 0: return (uint8_t)(a + b);      /* 3'b000 ADD (carry is dropped) */
    case 1: return (uint8_t)(a - b);      /* 3'b001 SUB (wraps mod 256)    */
    case 2: return (uint8_t)(a & b);
    case 3: return (uint8_t)(a | b);
    case 4: return (uint8_t)(a ^ b);
    case 5: return (uint8_t)~a;
    case 6: return (uint8_t)(a << 1);
    case 7: return (uint8_t)(a < b ? 1 : 0); /* unsigned set-less-than */
    default: return 0;
    }
}

int main(void) {
    const char *name[8] = {"ADD", "SUB", "AND", "OR", "XOR", "NOT a", "SHL a", "SLTU"};
    const uint8_t A[3] = {0x5A, 0x3C, 0xFF}, B[3] = {0x3C, 0x5A, 0x01};
    printf("op  name    a=5A b=3C   a=3C b=5A   a=FF b=01\n");
    for (unsigned op = 0; op < 8; op++) {
        printf("%u   %-6s", op, name[op]);
        for (int k = 0; k < 3; k++) {
            uint8_t y = alu(A[k], B[k], op);
            printf("   %02X zero=%d", (unsigned)y, y == 0);
        }
        printf("\n");
    }
    return 0;
}

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

op  name    a=5A b=3C   a=3C b=5A   a=FF b=01
0   ADD      96 zero=0   96 zero=0   00 zero=1
1   SUB      1E zero=0   E2 zero=0   FE zero=0
2   AND      18 zero=0   18 zero=0   01 zero=0
3   OR       7E zero=0   7E zero=0   FF zero=0
4   XOR      66 zero=0   66 zero=0   FE zero=0
5   NOT a    A5 zero=0   C3 zero=0   00 zero=1
6   SHL a    B4 zero=0   78 zero=0   FE zero=0
7   SLTU     00 zero=1   01 zero=0   00 zero=1

কয়েকটি সারি হাতে মিলিয়ে নিন। ADD: 5A + 3C = ৯০ + ৬০ = ১৫০ = 96; আর FF + 01 = ২৫৬, যা ৮ বিটে 00 — carry হারাল, তাই zero=1. (আসল ALU-তে এজন্যই আলাদা carry flag থাকে — পর্ব ১৩ আর ২০।) SUB: 3C − 5A = ৬০ − ৯০ = −৩০, যা ৮-বিট two's complement-এ ২৫৬ − ৩০ = ২২৬ = E2 (পর্ব ০৫)। NOT a: FF-এর উল্টো 00, তাই এখানেও zero flag জ্বলেছে। SHL: FF << 1 = 1 1111 1110, ৮ বিটে FE. SLTU: শুধু মাঝের কলামে 3C < 5A, তাই 01. একটি Verilog testbench-এ এই একই input দিয়ে alu চালালে প্রতিটি মান হুবহু মিলতে হবে — না মিললে ভুল হয় Verilog-এ, নয়তো reference model-এ; দুটিই খুঁজে দেখতে হয়।

৭ · Latch-এর ফাঁদ — অসম্পূর্ণ if আর case

Combinational circuit-এর সংজ্ঞা: output নির্ভর করে শুধু এই মুহূর্তের input-এর ওপর (combinational logic বলতে এটাই বোঝায়)। কোনো স্মৃতি নেই। এখন এই block-টি দেখুন:

always @(*) begin
    if (en)
        q = d;       // en = 0 হলে q-কে কিছুই বলা হয়নি
end

en = 1 হলে q = d. কিন্তু en = 0 হলে? Block-টি জাগে, if মেলে না, q-তে কিছুই লেখা হয় না। Verilog-এর নিয়ম বলে — যে variable-এ মান লেখা হয়নি, সে তার আগের মান ধরে রাখে। “আগের মান ধরে রাখা” মানে স্মৃতি, আর স্মৃতি মানে আর combinational নয়। Synthesis tool তখন লেখার অর্থ রক্ষা করতে একটি latch বসায় — একটি level-sensitive স্মৃতি-উপাদান, যা en = 1 থাকাকালীন d-কে পার হতে দেয় আর en = 0 হলে শেষ মানটি আটকে রাখে (পর্ব ১৫-এ D latch বিস্তারিত)। একে বলে inferred latch — আপনি চাননি, tool অনুমান করে বসিয়েছে।

Latch নিজে খারাপ জিনিস নয়, কিন্তু অনিচ্ছাকৃত latch প্রায় সবসময় ভুল: (১) চেয়েছিলেন gate, পেলেন স্মৃতি; (২) clock ছাড়া স্মৃতি থাকায় timing হিসাব (পর্ব ১৮) কঠিন হয়; (৩) en-এ একটি ছোট glitch-এও ভুল মান আটকে যেতে পারে। একই সমস্যা case-এ কোনো মান বাদ পড়লেও। চলুন একটি অসম্পূর্ণ case চালিয়ে দেখি, input বদলাতে থাকলে y কেমন আচরণ করে (a = 1, b = 0, c = 1 স্থির):

1always @(*) begin2  case (sel)3    2'b00: y = a;4    2'b01: y = b;5    2'b10: y = c;6    // 2'b11: কিছু লেখা নেই!7    default: y = 1'b0;  // ধাপ ৬-এর fix8  endcase9end
a = 1, b = 0, c = 1 (স্থির)
sel
যে শাখা মিলল
y
স্মৃতি লাগছে?
  1. sel = 00. প্রথম শাখা মিলল, y = a = 1. এখানে কোনো সমস্যা নেই।
  2. sel = 01. দ্বিতীয় শাখা, y = b = 0. এখনো ঠিক আছে — প্রতিটি নির্দিষ্ট sel-এ y-এর মান স্পষ্ট।
  3. sel = 11. কোনো শাখা মেলে না (লাইন ৬-এ কিছু লেখা নেই)। y-তে কিছুই লেখা হয় না, তাই y তার আগের মান 0 ধরে রাখে — যা এসেছিল b থেকে। এটি আর “sel আর a, b, c থেকে y”-এর কোনো সূত্র নয়; উত্তর নির্ভর করছে অতীতের ওপর।
  4. sel = 10. তৃতীয় শাখা, y = c = 1.
  5. sel = 11 আবার. একই input (sel = 11, a = 1, b = 0, c = 1), কিন্তু এবার y = 1, কারণ এবারের “আগের মান” 1. একই input-এ দুই রকম output — এটিই স্মৃতির প্রমাণ। Synthesis tool এখানে y-এর জন্য একটি latch বসাবে, যার enable হলো “sel ≠ 11”. Yosys-এর মতো tool এমন ক্ষেত্রে সাধারণত “Latch inferred for signal …” ধরনের বার্তা ছাপে — এই বার্তা কখনো উপেক্ষা করবেন না।
  6. Fix: default যোগ করা। লাইন ৭ যোগ করলে sel = 11-এ y = 0 — প্রতিবার। এখন প্রতিটি সম্ভাব্য input-এর জন্য y-এর একটি নির্দিষ্ট মান আছে, স্মৃতির দরকার নেই, তাই tool শুধু একটি 4:1 mux বসায়, যার চতুর্থ input-এ 0 জোড়া। আরেকটি সমান ভালো উপায়: block-এর প্রথম লাইনে y = 1'b0; লেখা, তারপর case.

এবার একই ধারণা একটি ছোট explainer দিয়ে দেখা যাক। নিচের JavaScript-এ একটি always @(*) block ডেটা হিসেবে লেখা — শাখার তালিকা (শর্ত আর মান) আর একটি ঐচ্ছিক else/default অংশ। Program-টি সব input-এর truth table বানায়, আর যে সারিতে কোনো শাখা মান দেয় না সেখানে লেখে “q_prev” — আগের মান। তারপর সময়ের সাথে একটি input-ধারা চালিয়ে দুই নকশার q পাশাপাশি দেখায়:

// Latch-inference explainer. A combinational always block is written as data:
// a list of branches [condition, value]; "dflt" is the else/default part (or null).
const leaky = { code: "if (en) q = d;",               branches: [[s => s.en === 1, s => s.d]], dflt: null };
const fixed = { code: "if (en) q = d; else q = 1'b0;", branches: [[s => s.en === 1, s => s.d]], dflt: () => 0 };

function evalBlock(blk, s, prev) {             // what does q become for inputs s?
  for (const [cond, val] of blk.branches) if (cond(s)) return { q: val(s), set: true };
  return blk.dflt ? { q: blk.dflt(s), set: true } : { q: prev, set: false };
}
function truthTable(blk) {
  console.log(`== always @(*) ${blk.code}`);
  console.log("   en d | q");
  let holds = 0;
  for (const en of [0, 1])
    for (const d of [0, 1]) {
      const r = evalBlock(blk, { en, d }, "q_prev");
      if (!r.set) holds++;
      console.log(`   ${en}  ${d} | ${r.set ? r.q : "q_prev  <- no assignment: keep old value"}`);
    }
  console.log(holds ? `   ${holds} row(s) need memory -> synthesis infers a LATCH`
                    : "   every row assigns q -> pure combinational logic, no latch");
}
truthTable(leaky);
truthTable(fixed);

const inputs = [[1, 1], [0, 0], [0, 1], [1, 0], [0, 1]];   // (en, d) over time
let qL = 0, qF = 0;
console.log("time  en d | q(leaky) q(fixed)");
inputs.forEach(([en, d], t) => {
  qL = evalBlock(leaky, { en, d }, qL).q;
  qF = evalBlock(fixed, { en, d }, qF).q;
  console.log(`  ${t * 10}`.padEnd(6) + `  ${en}  ${d} |    ${qL}        ${qF}`);
});

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

== always @(*) if (en) q = d;
   en d | q
   0  0 | q_prev  <- no assignment: keep old value
   0  1 | q_prev  <- no assignment: keep old value
   1  0 | 0
   1  1 | 1
   2 row(s) need memory -> synthesis infers a LATCH
== always @(*) if (en) q = d; else q = 1'b0;
   en d | q
   0  0 | 0
   0  1 | 0
   1  0 | 0
   1  1 | 1
   every row assigns q -> pure combinational logic, no latch
time  en d | q(leaky) q(fixed)
  0     1  1 |    1        1
  10    0  0 |    1        0
  20    0  1 |    1        0
  30    1  0 |    0        0
  40    0  1 |    0        0

প্রথম truth table-এ en = 0-এর দুটি সারিতে (d = 0 আর d = 1) q-এর কোনো মান নেই — q_prev, অর্থাৎ “যা ছিল তাই”। যে truth table-এ কোনো সারির উত্তর “আগের মান”, সেটি কোনো Boolean function নয়; সেটি বানাতে স্মৃতি লাগে — তাই 2 row(s) need memory -> synthesis infers a LATCH. দ্বিতীয় নকশায় else q = 1'b0 থাকায় চারটি সারিতেই মান আছে, ফলটি সরল q = en · d — একটি AND gate. সময়ের টেবিলে পার্থক্যটি চোখে পড়ে: t = 10 আর t = 20-এ en = 0, d বদলাচ্ছে, কিন্তু leaky নকশার q আটকে আছে 1-এ (t = 0-এর মান), আর fixed নকশার q = 0. t = 30-এ en = 1, d = 0 আসতেই দুটি আবার একই। অর্থাৎ leaky নকশা আসলে একটি D latch — en = 1 হলে স্বচ্ছ, en = 0 হলে বন্ধ।

নিয়ম

Combinational always block-এ প্রতিটি output-কে প্রতিটি পথে মান দিতে হবে। সবচেয়ে সহজ উপায়: block-এর শুরুতেই প্রতিটি output-এর একটি default মান লিখুন, তারপর if/case. প্রতিটি if-এর সাথে else আর প্রতিটি case-এ default রাখাও একই কাজ করে। SystemVerilog-এ always_comb ব্যবহার করলে অনেক tool অনিচ্ছাকৃত latch-এর জন্য সতর্কবার্তা বা error দেয়। আর যদি সত্যিই কিছু মনে রাখা দরকার হয় — তবে latch নয়, clock-চালিত flip-flop ব্যবহার করুন (মডিউল ৩)।

৮ · Testbench — নকশাকে পরীক্ষায় বসানো, আর simulation বনাম synthesis

একটি testbench নিজেও একটি Verilog module, কিন্তু এর কোনো port নেই — এটি পুরো একটি “পরীক্ষাগার”। ভেতরে যা পরীক্ষা হবে সেই নকশাকে একটি instance হিসেবে বসানো হয় (প্রচলিত নাম dut — device under test), তারপর একটি initial block সময় ধরে ধরে input দেয় আর output যাচাই করে। এটি কখনো hardware হয় না; শুধু simulator-এ চলে। তাই এখানে এমন সব construct ব্যবহার করা যায় যা synthesis-এ চলে না। নিচে ৪-বিট adder-এর একটি সম্পূর্ণ, self-checking testbench:

`timescale 1ns/1ps
module tb_adder4;
    reg  [3:0] a, b;
    reg        cin;
    wire [3:0] sum;
    wire       cout;
    integer    i, j, errors;

    adder4 dut (.a(a), .b(b), .cin(cin), .sum(sum), .cout(cout));

    initial begin
        $dumpfile("adder4.vcd");          // waveform file for GTKWave
        $dumpvars(0, tb_adder4);
        errors = 0;
        cin    = 1'b0;
        for (i = 0; i < 16; i = i + 1) begin
            for (j = 0; j < 16; j = j + 1) begin
                a = i[3:0];  b = j[3:0];
                #10;                         // wait 10 ns: let the adder settle
                if ({cout, sum} !== i + j) begin
                    $display("FAIL: %0d + %0d -> cout=%b sum=%b", i, j, cout, sum);
                    errors = errors + 1;
                end
                if (i == 9 && j == 7)
                    $display("t=%0d ns  a=%b b=%b -> cout=%b sum=%b", $time, a, b, cout, sum);
            end
        end
        $display("%0d tests, %0d errors", 256, errors);
        $finish;
    end
endmodule

প্রতিটি অংশ কী করে:

  • `timescale 1ns/1ps — সময়ের একক: #10 মানে ১০ ns, আর হিসাবের সূক্ষ্মতা ১ ps.
  • Input গুলো reg, output গুলো wire — কারণ testbench নিজে initial block থেকে input-এ মান লেখে, আর output চালায় dut.
  • initial begin … end — simulation শুরুর মুহূর্তে একবার চলে, ওপর থেকে নিচে (always চলে বারবার)।
  • #10; — delay: ১০ ns অপেক্ষা। এই সময়ে simulator dut-এর সব assign হিসাব করে নেয়। Delay না দিলে পুরোনো মান পড়ার ঝুঁকি থাকত।
  • দুই স্তরের for loop — সব ২৫৬টি জোড়া, অর্থাৎ exhaustive পরীক্ষা। i[3:0] লিখে ৩২-বিট integer থেকে নিচের ৪ বিট নেওয়া হয়েছে।
  • {cout, sum} !== i + j — self-checking: প্রত্যাশিত মান testbench নিজেই হিসাব করে, মানুষকে চোখ দিয়ে দেখতে হয় না। !== (চার-মান তুলনা) ব্যবহার করা হয়েছে যাতে output-এ x বা z এলেও FAIL ধরা পড়ে; সাধারণ !=-এ x এলে ফল হয় x, আর if সেটিকে মিথ্যা ধরে — ভুল চুপচাপ পার পেয়ে যেত।
  • $display — C-এর printf-এর মতো; %b binary, %0d দশমিক, $time বর্তমান সময়। $monitor একবার লিখলে তালিকার যেকোনো সংকেত বদলালেই নিজে থেকে একটি লাইন ছাপে — ছোট পরীক্ষায় সুবিধাজনক: initial $monitor("t=%0d a=%b b=%b sum=%b", $time, a, b, sum);
  • $dumpfile/$dumpvars — প্রতিটি সংকেতের সময়-বনাম-মান একটি VCD ফাইলে লেখে, যা GTKWave-এর মতো viewer-এ waveform হিসেবে দেখা যায়।
  • $finish — simulation শেষ। এটি না থাকলে কিছু testbench (যেমন clock চালানো) চিরকাল চলতে পারে।

নিজের কম্পিউটারে Icarus Verilog দিয়ে চারটি ফাইল (half_adder, full_adder, adder4 আর testbench) একসাথে compile করে চালালে আউটপুট এমন হওয়ার কথা। এটি উদাহরণ — এই পাতায় চালানো হয়নি; সংখ্যাগুলো উপরের JavaScript simulator-এর সাথে মেলে, শেষ লাইনের রূপ Icarus-এর সংস্করণভেদে সামান্য আলাদা হতে পারে:

$ iverilog -o adder4_tb.vvp tb_adder4.v adder4.v full_adder.v half_adder.v
$ vvp adder4_tb.vvp
VCD info: dumpfile adder4.vcd opened for output.
t=1520 ns  a=1001 b=0111 -> cout=1 sum=0000
256 tests, 0 errors
tb_adder4.v:29: $finish called at 2560000 (1ps)

সময়গুলো হিসাব করে দেখুন: (i, j) = (৯, ৭) হলো ৯ × ১৬ + ৭ = ১৫১তম জোড়া (০ থেকে গুনলে), তার আগে ১৫১টি আর নিজের একটি #10 মিলে ১৫২ × ১০ = ১৫২০ ns. আর পুরো ২৫৬টি জোড়ায় ২৫৬ × ১০ = ২৫৬০ ns = ২৫,৬০,০০০ ps — শেষ লাইনে সময় ps-এ (সূক্ষ্মতার এককে) ছাপা হয়।

Simulation বনাম synthesis — কোনটা hardware হয়, কোনটা হয় না

Verilog প্রথমে তৈরি হয়েছিল simulation-এর ভাষা হিসেবে; synthesis পরে এসে তার একটি অংশকে বেছে নিয়েছে। তাই ভাষার সবকিছু hardware-এ রূপান্তরযোগ্য নয়:

ConstructSimulation-এSynthesis-এ
#10 delayসময় অপেক্ষা করেউপেক্ষা করা হয় (সাধারণত সতর্কবার্তাসহ) — gate-এর delay ঠিক করে প্রযুক্তি, কোড নয়
initial blockশুরুতে একবার চলেASIC-এ উপেক্ষিত (শুরুর মান দিতে reset লাগে); কিছু FPGA tool register-এর শুরুর মান হিসেবে মানে
$display, $monitor, $finish, $dumpvarsলেখা, শেষ করা, waveformকিছুই না — শুধু simulator-এর জন্য
===, !== (x/z তুলনা)কাজ করেসাধারণত অসমর্থিত — hardware-এ x নেই
স্থির সীমার for, generateloopখুলে N কপি hardware
assign, always @(*), case, operatorকাজ করেgate, mux, adder — FPGA-তে LUT

Synthesis tool আসলে কী দেয়? একটি netlist — একটি লক্ষ্য-প্রযুক্তির মৌলিক উপাদানের তালিকা আর তাদের সংযোগ। ASIC-এর জন্য সেই উপাদান হলো কারখানার standard cell (NAND2, XOR2, flip-flop …); FPGA-র জন্য LUT, flip-flop আর বিশেষ ব্লক। পথে tool Boolean সরলীকরণ করে আর timing-এর লক্ষ্য মেলাতে কাঠামো বদলায়।

একটু আগাম দেখা: clock আর <=

এই পর্বের সব নকশা combinational — কোনো স্মৃতি নেই। স্মৃতিওয়ালা (sequential) circuit-এর জন্য Verilog-এ আরেক রকম block আছে, যা মডিউল ৩-এ (পর্ব ১৫–১৯) বিস্তারিত শিখবেন। শুধু চেহারাটা চিনে রাখুন:

always @(posedge clk)    // only at the rising edge of the clock
    q <= d;              // non-blocking: q takes d at the clock edge

@(posedge clk) মানে block-টি শুধু clock-এর উঠন্ত ধারে জাগে — অন্য সময় q যা আছে তাই, অর্থাৎ এটি একটি flip-flop. আর <= হলো non-blocking assignment: একই clock ধারের সব <= আগে ডান পাশ পড়ে নেয়, তারপর একসাথে বাঁ পাশে বসায় — যেমন বাস্তবে সব flip-flop একই মুহূর্তে মান নেয়। সংক্ষিপ্ত নিয়ম, যা পর্ব ১৫–১৭-এ বারবার কাজে লাগবে: combinational → always @(*) + =; sequential → always @(posedge clk) + <=. দুটি মেশালে simulation আর hardware-এর অমিলের মতো কঠিন ভুল তৈরি হয়। পর্ব ১৭-এ FSM আর পর্ব ১৯-এর traffic light প্রজেক্টে এই দুই ধরনের block একসাথে ব্যবহার হবে।

৯ · Tool, FPGA আর কাজের জগৎ

Verilog শিখতে দামি software লাগে না। কয়েকটি পরিচিত বিনামূল্যের (open-source) tool:

Toolকাজমনে রাখার কথা
Icarus Verilog (iverilog, vvp)Event-driven simulator: iverilog compile করে, vvp চালায়শেখার জন্য সবচেয়ে সহজ; SystemVerilog-এর কিছু অংশ -g2012 দিয়ে
VerilatorVerilog-কে C++-এ রূপান্তর করে খুব দ্রুত simulation; শক্তিশালী lint (--lint-only -Wall)মূলত synthesizable অংশ বোঝে; বড় CPU নকশার simulation-এ জনপ্রিয়
YosysSynthesis — Verilog থেকে gate বা FPGA-র LUT-এর netlist; stat দিয়ে সংখ্যাকিছু FPGA-র জন্য পুরো open-source প্রবাহের অংশ (place-and-route-এর জন্য nextpnr-এর মতো tool)
GTKWaveVCD ফাইল থেকে waveform দেখাসময়ের সাথে কোন তারে কী — ভুল খোঁজার চোখ
EDA Playground-এর মতো online simulatorBrowser-এ Verilog লিখে চালানোকিছু সেবায় বিনামূল্যে account লাগে; শর্ত বদলাতে পারে — ব্যবহারের আগে দেখে নিন

এই পাতার runner-এ এগুলোর কোনোটি আছে কি না, নিচের bash script খুঁজে দেখে — প্রতিটি পরীক্ষা command -v দিয়ে সুরক্ষিত, তাই না থাকলেও script ভাঙবে না:

# Are open-source Verilog tools installed on this runner? Every check is guarded.
echo "machine: $(uname -m)"
found=0
for tool in iverilog vvp verilator yosys gtkwave; do
  if command -v "$tool" >/dev/null 2>&1; then
    echo "  $tool : found"; found=$((found + 1))
  else
    echo "  $tool : not installed here"
  fi
done
echo "Verilog tools found: $found of 5"
if command -v gcc >/dev/null 2>&1; then
  echo "gcc: found (a C compiler, not a Verilog simulator)"
else
  echo "gcc: not found either"
fi
if [ "$found" -eq 0 ]; then
  echo "=> Run the Verilog on this page on your own computer or in an online simulator."
fi

একটি run-এর আউটপুট:

machine: x86_64
  iverilog : not installed here
  vvp : not installed here
  verilator : not installed here
  yosys : not installed here
  gtkwave : not installed here
Verilog tools found: 0 of 5
gcc: found (a C compiler, not a Verilog simulator)
=> Run the Verilog on this page on your own computer or in an online simulator.

সৎ উত্তর: runner-টি একটি cloud সার্ভারের x86-64 container, যেখানে C compiler আছে কিন্তু কোনো Verilog tool নেই — পাঁচটির মধ্যে শূন্য। এজন্যই এই পর্বের Verilog কোডগুলো “উদাহরণ” হিসেবে দেখানো, আর চালানোর জন্য JavaScript-এর ছোট simulator আর C-এর reference model ব্যবহার করা হয়েছে। আসল Verilog চালাতে নিজের কম্পিউটারে tool বসান (নিচে), অথবা একটি online simulator ব্যবহার করুন।

Ubuntu বা Debian-ধাঁচের Linux-এ (Windows-এ WSL দিয়েও) সাধারণত package manager দিয়েই বসানো যায়। এটি উদাহরণ — package-এর নাম ও সংস্করণ distribution ভেদে আলাদা হতে পারে:

$ sudo apt install iverilog gtkwave yosys verilator
$ iverilog -g2012 -o tb.vvp tb_adder4.v adder4.v full_adder.v half_adder.v && vvp tb.vvp
$ gtkwave adder4.vcd                               # waveform
$ verilator --lint-only -Wall alu.v                # warnings: width, latch, unused...
$ yosys -p "synth -top adder4; stat" adder4.v full_adder.v half_adder.v

FPGA-র ভেতরে কী আছে

অংশ ১-এ দেখেছেন, FPGA-তে gate নেই, আছে LUT (look-up table). একটি k-input LUT আসলে ২k বিটের একটি ছোট মেমরি: k টি input মিলে একটি ঠিকানা, আর সেই ঠিকানায় রাখা বিটটিই output. তাই k টি input-এর যেকোনো Boolean function একটি LUT-এ বসে যায় — শুধু truth table-টি মেমরিতে লিখে দিলেই হলো। ৪-input LUT-এ ১৬ বিট, ৬-input LUT-এ ৬৪ বিট; আধুনিক অনেক FPGA ৬-input বা তার কাছাকাছি LUT ব্যবহার করে। এর সাথে FPGA-তে থাকে:

  • Flip-flop — প্রতিটি LUT-এর পাশে সাধারণত এক বা একাধিক; sequential নকশার স্মৃতি (মডিউল ৩)।
  • Programmable routing — হাজার হাজার তার আর switch, যা ঠিক করে কোন LUT-এর output কোন LUT-এর input-এ যাবে।
  • বিশেষ ব্লক — block RAM (মেমরি), DSP ব্লক (দ্রুত গুণ), clock-এর ব্যবস্থা, আর বাইরের সাথে যোগাযোগের I/O pin.

এই সব উপাদানের প্রতিটি switch আর প্রতিটি LUT-এর বিট কী হবে, তা লেখা থাকে bitstream-এ। বেশিরভাগ FPGA-তে এই configuration SRAM-এ থাকে, তাই বিদ্যুৎ গেলে মুছে যায় আর চালু হওয়ার সময় একটি flash chip থেকে আবার load হয়। নতুন নকশা দিলে একই chip অন্য circuit হয়ে যায় — prototype, ছোট পরিসরের পণ্য আর গবেষণায় এই নমনীয়তাই FPGA-র শক্তি। Microcontroller, SoC আর embedded সিস্টেমের কথা পর্ব ৫৬-এ আসবে।

আরও গভীরে: বড় chip কোম্পানিতে নকশার কাজ সাধারণত দুই ভাগে চলে — RTL design আর verification — আর অনেক প্রকল্পে verification-এর লোক ও সময় design-এর সমান বা বেশি লাগে বলে শিল্পে প্রায়ই বলা হয়। Verification-এ SystemVerilog-এর class, random input তৈরি, coverage মাপা আর UVM (Universal Verification Methodology) নামের একটি প্রচলিত কাঠামো ব্যবহার হয়; পাশাপাশি formal verification গাণিতিকভাবে প্রমাণ করে যে কোনো নির্দিষ্ট ভুল অবস্থা কখনো ঘটবে না।

কাজের জগৎ

HDL জানা থাকলে কয়েক ধরনের কাজের দরজা খোলে: RTL design engineer (Verilog/SystemVerilog-এ block নকশা), design verification engineer (testbench, UVM, coverage — সংখ্যায় সম্ভবত সবচেয়ে বেশি পদ), FPGA engineer (টেলিকম, শিল্প-নিয়ন্ত্রণ, প্রতিরক্ষা, গবেষণা), আর synthesis-পরবর্তী physical design. বাংলাদেশে কয়েকটি প্রতিষ্ঠান বিদেশি chip কোম্পানির জন্য design, verification আর physical design-এর সেবা দেয় বলে জানা যায়, আর বিশ্ববিদ্যালয়গুলোতে VLSI ও FPGA-ভিত্তিক গবেষণাও চলছে; তবে সুযোগের ধরন ও সংখ্যা সময়ের সাথে বদলায় — নিজে খোঁজ নিয়ে সাম্প্রতিক তথ্য যাচাই করুন। প্রস্তুতির সবচেয়ে ভালো প্রমাণ নিজের একটি প্রকল্প — যেমন একটি সরল CPU Verilog-এ লিখে testbench দিয়ে যাচাই করা।

পরীক্ষা ও ইন্টারভিউতে — কয়েকটি প্রচলিত ধরনের প্রশ্ন আর এক লাইনের উত্তর:

  • wire আর reg-এর পার্থক্য কী? reg মানেই কি flip-flop? wire চালায় assign বা module output; reg মান পায় procedural block-এ — combinational always @(*)-এ reg কোনো flip-flop নয়।
  • Blocking (=) আর non-blocking (<=)-এর পার্থক্য? = সঙ্গে সঙ্গে বসায় (combinational block-এ); <= ডান পাশ তখনই পড়ে, কিন্তু বাঁ পাশে বসায় সেই মুহূর্তের (time step) সব হিসাব শেষে, সবগুলো একসাথে (clock-চালিত block-এ)।
  • Inferred latch কেন হয়, কীভাবে এড়াবেন? Combinational block-এর কোনো পথে output মান না পেলে; শুরুতে default, প্রতিটি if-এ else, case-এ default.
  • কোন construct synthesizable নয়? # delay, $display/$finish, (ASIC-এ) initial, ===; স্থির সীমার for বা generate কিন্তু synthesizable.
  • একটি k-input LUT কী, কতগুলো function বানাতে পারে? ২k বিটের মেমরি; k input-এর যেকোনো function — মোট ২(২k)টি।

১০ · AI-এর যুগে — Verilog AI দিয়ে, যাচাই simulation দিয়ে

AI-কে একটি 4:1 mux-এর Verilog লিখে দিতে বললে কয়েক সেকেন্ডে উত্তর আসে, আর ছোট নকশায় প্রায়ই ঠিকও হয়। কিন্তু hardware-এ ভুলের দাম software-এর চেয়ে অনেক বেশি — chip একবার তৈরি হলে “patch” দেওয়া যায় না। এই বিষয়ে AI-এর সাধারণ ভুলগুলো:

  • Non-synthesizable কোড: নকশার ভেতরে #5 delay, initial দিয়ে মান বসানো, বা testbench-এর construct মিশিয়ে দেওয়া।
  • Latch: combinational block-এ else বা default বাদ দেওয়া; একাধিক output-এর মধ্যে কোনোটিকে কোনো শাখায় মান না দেওয়া।
  • = আর <= গুলিয়ে ফেলা: clock-চালিত block-এ =, বা combinational block-এ <=.
  • বিট-মাপের ভুল: carry হারানো (৪-বিট যোগফল ৪-বিট তারে), signed/unsigned গুলিয়ে তুলনা, [0:3] আর [3:0] উল্টে ফেলা।
  • C-এর মতো ভাবা: assign-এর ক্রমে অর্থ খোঁজা, বা hardware-এ চলার সময় বদলানো loop সীমা লেখা।
  • বানানো তথ্য: FPGA-র LUT সংখ্যা, chip-এর spec, tool-এর option বা সংস্করণের নাম ভুল বলা।

AI-কে নকশাকার নয়, সহকারী হিসেবে ব্যবহার করুন — সাথে testbench আর ব্যাখ্যা চেয়ে নিন:

আমি Verilog শিখছি। নিচের circuit-এর জন্য synthesizable Verilog-2001 (বা SystemVerilog) কোড লেখো, এই শর্তে: ১. শুধু synthesizable construct ব্যবহার করবে — নকশার ভেতরে কোনো # delay, initial বা $display নয়। ২. combinational অংশ always @(*) (বা always_comb) আর = দিয়ে; প্রতিটি output-এর শুরুতে default মান দেবে, যাতে latch না হয়। ৩. প্রতিটি port-এর বিট-মাপ আর signed/unsigned স্পষ্ট লিখবে; যোগফলের carry কোথায় যাচ্ছে বলবে। ৪. আলাদা একটি self-checking testbench দেবে, যা সব input (বা ছোট নকশায় exhaustive) চালিয়ে প্রত্যাশিত মানের সাথে মেলায় আর শেষে "N tests, M errors" ছাপে। ৫. শেষে বলো Icarus Verilog আর Verilator --lint-only দিয়ে কীভাবে চালাব, আর কোন সতর্কবার্তা আসতে পারে। Circuit: [এখানে আপনার circuit-এর বর্ণনা লিখুন]

AI-এর দেওয়া Verilog যাচাইয়ের চেকলিস্ট

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

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

প্র ০১একটি ছোট কোম্পানির simulator-এর ভাষা Verilog কীভাবে শিল্পের মান হয়ে গেল, আর VHDL কেন পাশাপাশি টিকে রইল?

১৯৮০-র দশকের শুরুতে chip-এর নকশা মূলত schematic আর হাতে লেখা netlist-নির্ভর ছিল, আর simulator-গুলো ছিল প্রতিটি কোম্পানির নিজস্ব। Gateway Design Automation-এ তৈরি Verilog ছিল এমনই একটি নিজস্ব ভাষা, তাদের Verilog-XL simulator-এর সাথে। এর গঠন C-এর কাছাকাছি হওয়ায় প্রকৌশলীরা সহজে শিখতেন, আর simulator-টি দ্রুত হওয়ায় বাণিজ্যিক chip শিল্পে জনপ্রিয় হয়ে ওঠে। ১৯৮০-র দশকের শেষে Synopsys-এর মতো কোম্পানির synthesis tool Verilog বর্ণনা থেকে gate বানাতে শুরু করে — তখন থেকে ভাষাটি শুধু যাচাইয়ের নয়, নকশারও ভাষা হয়ে যায়।

একই সময়ে মার্কিন প্রতিরক্ষা বিভাগ VHSIC কর্মসূচির অংশ হিসেবে চাইছিল এমন একটি ভাষা, যাতে সরবরাহকারীদের chip-এর নকশা দীর্ঘ সময় ধরে নির্ভুলভাবে নথিভুক্ত থাকে — কোনো একটি কোম্পানির tool-এর ওপর নির্ভর না করে। এভাবেই VHDL, যা ১৯৮৭ সালে IEEE মান হয়। Verilog তখনো ছিল Cadence-এর মালিকানায় (১৯৮৯ সালের দিকে Gateway কেনার পর)। প্রতিযোগী একটি উন্মুক্ত মান থাকায় চাপ তৈরি হয়, আর Cadence ১৯৯০ সালে Verilog উন্মুক্ত করে; Open Verilog International নামের একটি সংগঠন এর দায়িত্ব নেয়, আর ১৯৯৫ সালে এটি IEEE 1364 হয়।

দুটি ভাষা টিকে থাকার কারণ প্রযুক্তিগত যতটা, ততটাই সামাজিক। VHDL-এর কড়া type-নিয়ম দীর্ঘমেয়াদি, নিরাপত্তা-সংবেদনশীল প্রকল্পে (মহাকাশ, প্রতিরক্ষা, রেল) ভুল কমায় — এ ধরনের শিল্পে আর ইউরোপের অনেক প্রতিষ্ঠানে এটি গেঁথে গেছে। Verilog-এর সংক্ষিপ্ততা দ্রুত কাজের বাণিজ্যিক chip শিল্পে পছন্দের, আর SystemVerilog যোগ হওয়ার পর verification-এর জগতে এর প্রভাব আরও বেড়েছে। আজকের বড় tool প্রায় সবই দুটি ভাষা — এমনকি একই প্রকল্পে মেশানো — সমর্থন করে। একবার একটি ভাষায় লক্ষ লক্ষ লাইনের পুরোনো নকশা আর যাচাই-করা library জমে গেলে, সেটি ছেড়ে যাওয়ার খরচ অনেক।

মূল উপলব্ধি: মান (standard) প্রায়ই সবচেয়ে “সুন্দর” প্রযুক্তি থেকে নয়, ব্যবহার, tool আর প্রতিযোগিতার চাপ থেকে জন্মায় — আর একবার জন্মালে পুরোনো নকশার ভারে বহু দশক টিকে থাকে।

প্র ০২Verilog-এ reg শব্দটি register বোঝায় না — এই বিভ্রান্তিকর নাম কেন এল, আর এর থেকে কী ধরনের ভুল হয়?

Verilog প্রথমে ছিল একটি simulation ভাষা। Simulator-এর দৃষ্টিতে দুই ধরনের জিনিস আছে: তার (net), যার মান সবসময় তার চালকের ওপর নির্ভর করে, আর variable, যেখানে কোনো procedural কোড মান লেখে আর পরের লেখা পর্যন্ত সেই মান থেকে যায়। দ্বিতীয়টি “মান ধরে রাখে”, তাই নাম দেওয়া হয়েছিল reg. Simulation-এর ভাষায় এটি যুক্তিসংগত; সমস্যা শুরু হয় যখন synthesis এসে একই কোড থেকে hardware বানাতে শুরু করে।

Synthesis tool reg শব্দ দেখে কিছু ঠিক করে না; সে দেখে block-টি কীভাবে লেখা। always @(posedge clk)-এ মান পেলে flip-flop; always @(*)-এ প্রতিটি পথে মান পেলে সাধারণ gate; কোনো পথে মান না পেলে latch. অর্থাৎ একই reg তিন রকম hardware হতে পারে। নতুনরা দুই দিক থেকে ভুল করেন। কেউ ভাবেন “reg লিখলেই register”, তাই combinational output-এর জন্য wire খুঁজতে গিয়ে অপ্রয়োজনীয় assign জুড়ে দেন। আবার কেউ ভাবেন “আমি তো wire চেয়েছি, reg লিখেছি শুধু always-এর জন্য” — অথচ অসম্পূর্ণ if-এর কারণে সেখানে সত্যিই একটি স্মৃতি তৈরি হয়ে গেছে।

Verilog-2001-এ এই নামের সমস্যা স্বীকার করে মানের ভাষায় “variable” শব্দটি বেশি ব্যবহার শুরু হয়, আর SystemVerilog আনে logic — একটি সাধারণ type, যা assign বা always যেকোনো জায়গা থেকে (একটিমাত্র চালক থাকলে) মান পেতে পারে। সঙ্গে আসে always_comb, always_ff আর always_latch — নকশাকারী নিজে বলে দেন কোন ধরনের hardware চান, আর tool মেলাতে না পারলে সতর্ক করে। এভাবে “ইচ্ছা” আর “লেখা”-র মধ্যের ফাঁক কমানো হয়েছে।

মূল উপলব্ধি: HDL-এ শব্দের নাম নয়, লেখার গঠনই hardware ঠিক করে — তাই কোড পড়ার সময় সবসময় জিজ্ঞেস করুন, “এখান থেকে কী circuit তৈরি হবে?”

প্র ০৩Simulation-এ সব ঠিক, কিন্তু chip বা FPGA-তে ভুল — এই “simulation-synthesis mismatch” কীভাবে ঘটে?

Simulator আর synthesis tool একই Verilog পড়ে, কিন্তু দুজনের উদ্দেশ্য আলাদা। Simulator ভাষার নিয়ম অক্ষরে অক্ষরে মানে — কোন ঘটনায় কোন block জাগবে, x কীভাবে ছড়াবে। Synthesis tool খোঁজে একটি circuit, যা “স্বাভাবিক অর্থে” লেখাটির সমান। যেখানে এই দুই ব্যাখ্যা আলাদা, সেখানেই mismatch — আর এটি বিপজ্জনক, কারণ testbench সবুজ সংকেত দিলেও আসল hardware ভুল।

সবচেয়ে পুরোনো উদাহরণ অসম্পূর্ণ sensitivity list. Verilog-1995-এ always @(a or b) লিখে ভেতরে c-ও পড়লে simulator c বদলালে block জাগায় না, তাই simulation-এ output পুরোনো থাকে; কিন্তু synthesis tool তালিকা উপেক্ষা করে পুরো combinational circuit বানায়, যা c বদলালেই বদলায়। Verilog-2001-এর @(*) এই ভুল দূর করতেই আসে। দ্বিতীয় উদাহরণ initial: simulation-এ register শুরুতে একটি মান পায়, কিন্তু ASIC-এ সেটি উপেক্ষিত, তাই chip চালু হলে মান এলোমেলো — reset না থাকলে নকশা অন্যরকম আচরণ করে। তৃতীয়টি synthesis-এর বিশেষ নির্দেশ, যেমন full_case আর parallel_case pragma: এগুলো tool-কে বলে “বাকি ঘরগুলো নিয়ে ভাবার দরকার নেই”, কিন্তু simulator এগুলো মন্তব্য হিসেবে উপেক্ষা করে। ১৯৯৯ সালের দিকে Clifford Cummings-এর একটি বহুল উদ্ধৃত সম্মেলন-প্রবন্ধ এই দুটিকে “evil twins” বলে সতর্ক করেছিল বলে জানা যায়।

আরেকটি সূক্ষ্ম উৎস x-এর আচরণ। if (sel)-এ sel = x হলে simulator সাধারণত else শাখায় যায় — “x-optimism” — ফলে একটি অজানা মান লুকিয়ে যায়; কিন্তু hardware-এ sel হয় 0 নয় 1, আর কোনটি হবে কেউ জানে না। এজন্য শিল্পে synthesis-এর পর netlist নিয়ে আবার simulation (gate-level simulation), equivalence checking (RTL আর netlist গাণিতিকভাবে সমান কি না যাচাই) আর lint tool নিয়মিত ব্যবহার হয়।

মূল উপলব্ধি: testbench পাস করা মানে “simulator-এর চোখে ঠিক”, “hardware-এ ঠিক” নয় — দুটির ফাঁক বন্ধ করতে synthesizable রীতি মেনে লেখা আর synthesis-এর পরে আবার যাচাই করা দরকার।

প্র ০৪একটি নকশা FPGA-তে রাখবেন, নাকি ASIC বানাবেন? কোন হিসাব থেকে সিদ্ধান্ত নেওয়া হয়?

দুটির খরচের গঠন একেবারে আলাদা। ASIC বানাতে শুরুতেই বিশাল একবারের খরচ — নকশা, যাচাই, mask তৈরি আর কারখানার কাজ — যাকে বলে NRE (non-recurring engineering) খরচ। আধুনিক প্রযুক্তিতে এটি বিপুল অঙ্কের হতে পারে, আর সময় লাগে মাসের পর মাস। কিন্তু তৈরি হওয়ার পর প্রতিটি chip-এর দাম কম, গতি বেশি আর বিদ্যুৎ খরচ অনেক কম, কারণ সেখানে শুধু দরকারি gate আছে। FPGA-তে NRE প্রায় নেই — board কিনে আজই নকশা চালানো যায়, ভুল হলে কয়েক মিনিটে নতুন bitstream. কিন্তু প্রতিটি chip দামি, আর একই কাজে FPGA সাধারণত ASIC-এর চেয়ে ধীর ও বেশি বিদ্যুৎ খরচ করে, কারণ নমনীয়তার জন্য প্রচুর routing আর অব্যবহৃত উপাদান বহন করতে হয়।

তাই সিদ্ধান্তের মূল প্রশ্ন সংখ্যা: কতগুলো chip লাগবে? কয়েকশো বা কয়েক হাজার হলে FPGA প্রায় সবসময় সস্তা; কোটি কোটি হলে (ফোনের SoC) ASIC ছাড়া উপায় নেই। মাঝামাঝি পরিমাণে হিসাবটি খরচ, সময়ে-বাজারে-আসা আর ভবিষ্যতে নকশা বদলানোর সম্ভাবনার ওপর নির্ভর করে। Bitcoin mining এর একটি পরিচিত দৃষ্টান্ত: শুরুতে CPU, তারপর GPU, কিছু সময় FPGA, আর শেষে বিশেষ ASIC — কাজটি স্থির আর বাজার বড় হওয়ায় শেষ ধাপে ASIC-ই জিতেছে।

অনেক প্রকল্পে দুটি একসাথেও চলে। ASIC বানানোর আগে নকশা FPGA-তে বসিয়ে আসল গতিতে পরীক্ষা করা হয় (FPGA prototyping), কারণ software simulation অনেক ধীর। আবার ডেটা সেন্টারে কিছু কোম্পানি network বা search-এর কাজ দ্রুত করতে FPGA ব্যবহার করেছে বলে জানা যায় — কাজের ধরন বদলালে hardware-ও নতুন করে লেখা যায়। আর মাঝামাঝি বিকল্পও আছে, যেমন structured ASIC বা FPGA-র ভেতরে বসানো প্রসেসর core.

মূল উপলব্ধি: FPGA বনাম ASIC আসলে নমনীয়তা বনাম দক্ষতার দর-কষাকষি, আর রায় দেয় উৎপাদনের পরিমাণ — একই Verilog দুই দিকেই যেতে পারে, এটাই HDL-এর বড় সুবিধা।

প্র ০৫Chisel-এর মতো hardware generator, high-level synthesis আর AI — Verilog লেখার কাজ কি একদিন হারিয়ে যাবে?

Hardware নকশার ইতিহাস হলো ধাপে ধাপে abstraction বাড়ানোর ইতিহাস। প্রথমে transistor-এর layout হাতে আঁকা হতো, তারপর gate-এর schematic, তারপর ১৯৮০–৯০-এর দশকে RTL আর synthesis. প্রতিবার নকশাকারীর কাজ একটু ওপরের স্তরে উঠেছে, আর নিচের স্তরটি tool-এর হাতে গেছে — কিন্তু নিচের স্তর বোঝার দরকার কখনো ফুরায়নি, কারণ সমস্যা হলে সেখানে নেমেই খুঁজতে হয়।

Chisel (UC Berkeley) এই ধারার একটি পরের ধাপ। Verilog-এর generate আর parameter দিয়ে কিছুটা “নকশার কাঠামো” লেখা যায়, কিন্তু Chisel একটি পূর্ণ প্রোগ্রামিং ভাষা (Scala) দিয়ে circuit তৈরি করার প্রোগ্রাম লিখতে দেয় — একই generator থেকে ভিন্ন মাপের cache বা ভিন্ন সংখ্যক core বের করা যায়। Berkeley-র RISC-V গবেষণায়, Rocket Chip আর BOOM core-এ, এটি ব্যবহার হয়েছে বলে জানা যায়। High-level synthesis (HLS) আরেক দিক থেকে আসে: C/C++-এর একটি algorithm থেকে tool নিজে pipeline আর hardware বানায়; signal processing-এর মতো নিয়মিত কাজে এটি বেশ সফল, কিন্তু নিয়ন্ত্রণ-প্রধান নকশায় (যেমন CPU-র control) এখনো RTL-এর মতো সূক্ষ্ম নিয়ন্ত্রণ দেয় না।

AI-এর ক্ষেত্রেও একই কথা খাটে। গবেষণা ও শিল্পে বড় ভাষা-মডেল দিয়ে Verilog লেখা আর testbench তৈরির পরীক্ষা চলছে, আর ছোট module-এ ফল প্রায়ই ভালো। কিন্তু hardware-এ “প্রায় ঠিক” চলে না — একটি latch, একটি হারানো carry বা একটি ভুল reset একটি পুরো chip নষ্ট করতে পারে। তাই AI যত বেশি কোড লিখবে, যাচাইয়ের দক্ষতা — testbench, coverage, formal verification, আর synthesis-এর ফল পড়তে পারা — তত মূল্যবান হবে। এই পর্বে যে ভিত্তি শিখলেন (concurrency, latch, simulation বনাম synthesis), সেটি যেকোনো উচ্চ-স্তরের tool বা AI-এর ফল বিচার করার মাপকাঠি।

এছাড়া open-source silicon-এর আন্দোলনও এই প্রশ্নকে প্রভাবিত করছে: কিছু কারখানা পুরোনো প্রযুক্তির নকশা-তথ্য (PDK) উন্মুক্ত করেছে, ফলে ছাত্র-গবেষকেরাও open-source tool দিয়ে নিজের ছোট chip নকশা করার সুযোগ পাচ্ছেন।

মূল উপলব্ধি: abstraction বাড়লে লেখার কাজ কমে, কিন্তু “এখান থেকে কী hardware হবে আর সেটি ঠিক কি না” বোঝার কাজ কমে না — সেটিই নকশাকারীর আসল দক্ষতা।

অনুশীলন

কাগজ-কলমে আগে নিজে করুন, তারপর পূর্ণ সমাধান মিলিয়ে নিন।

  1. হাতে হিসাব: a = 4'b1100, b = 4'b1010 হলে নিচের প্রতিটির মান ও বিট-সংখ্যা লিখুন: (ক) a & b, a | b, a ^ b, ~a (খ) &a, |b, ^b (গ) {a[1:0], b[3:2]}, {2{a[3:2]}} (ঘ) (a > b) ? a : b (ঙ) a + b-এর ফল ৪-বিট তারে আর ৫-বিট তারে।

    (ক) AND: 1100 · 1010 = 4'b1000; OR: 4'b1110; XOR: 4'b0110; NOT: 4'b0011 — সবই ৪ বিট।

    (খ) &a = 1·1·0·0 = 1'b0; |b = 1'b1 (অন্তত একটি 1 আছে); ^b = 1 ⊕ 0 ⊕ 1 ⊕ 0 = 1'b0 (দুটি 1 — জোড়)। প্রতিটি ১ বিট।

    (গ) a[1:0] = 00, b[3:2] = 10 → 4'b0010; a[3:2] = 11, দুবার → 4'b1111.

    (ঘ) ১২ > ১০ সত্য, তাই ফল a = 4'b1100.

    (ঙ) ১২ + ১০ = ২২ = binary 10110. ৪-বিট তারে শুধু নিচের চার বিট: 4'b0110 (= ৬, carry হারাল); ৫-বিট তারে 5'b10110 (= ২২)।

  2. আউটপুট অনুমান করুন: নিচের testbench কী ছাপবে? ($monitor প্রতিটি সময়-বিন্দুর শেষে, তালিকার কোনো মান বদলালে একবার ছাপে।)
    module t;
      reg a, b;  wire y;
      assign y = a ^ b;
      initial begin
        $monitor("t=%0d a=%b b=%b y=%b", $time, a, b, y);
        a = 0; b = 0;
        #5 a = 1;
        #5 b = 1;
        #5 a = 0;
        #5 $finish;
      end
    endmodule
    t=0 a=0 b=0 y=0
    t=5 a=1 b=0 y=1
    t=10 a=1 b=1 y=0
    t=15 a=0 b=1 y=1

    t = 0-এ a আর b দুটিই 0 পায়, assign সঙ্গে সঙ্গে y = 0 ⊕ 0 = 0 করে, আর $monitor সময়-বিন্দুর শেষে একবারই ছাপে (মাঝের x অবস্থা ছাপা হয় না)। t = 5-এ a = 1 → y = 1; t = 10-এ b = 1 → y = 1 ⊕ 1 = 0; t = 15-এ a = 0 → y = 1. t = 20-এ $finish, কোনো মান বদলায়নি, তাই আর কোনো লাইন নেই। লক্ষ করুন, y-এর কোনো মান testbench নিজে লেখেনি — assign নিজে থেকেই প্রতিবার হালনাগাদ হয়েছে।

  3. ভুল খুঁজুন: নিচের block থেকে কী hardware তৈরি হবে? কতগুলো latch, কোন সংকেতের জন্য? ঠিক করে লিখুন।
    always @(*) begin
        if (sel == 2'b00)      begin y = a; valid = 1'b1; end
        else if (sel == 2'b01)       y = b;
    end

    দুটি latch. y-এর জন্য: sel = 10 বা 11 হলে y কোনো মান পায় না। valid-এর জন্য: শুধু sel = 00-তে মান পায়; sel = 01, 10, 11-এ আগের মান ধরে রাখে — একবার 1 হলে চিরকাল 1 থেকে যেতে পারে, যা প্রায় নিশ্চিতভাবেই নকশাকারীর ইচ্ছা নয়।

    Fix — শুরুতে সব output-এর default:

    always @(*) begin
        y     = 1'b0;          // defaults: every path now assigns y and valid
        valid = 1'b0;
        if (sel == 2'b00)      begin y = a; valid = 1'b1; end
        else if (sel == 2'b01) begin y = b; valid = 1'b1; end
    end

    এখন y আর valid দুটিই প্রতিটি sel-এ নির্দিষ্ট মান পায় — শুধু কয়েকটি gate আর একটি ছোট mux, কোনো latch নেই। (sel = 01-এ valid = 1 হবে কি না, তা নকশার নির্দেশনার ওপর নির্ভর করে; মূল কথা হলো সিদ্ধান্তটি স্পষ্ট লেখা।) SystemVerilog-এ always_comb লিখলে মূল ভুল নকশায় tool সতর্ক করত।

  4. Circuit লিখুন: structural পদ্ধতিতে তিনটি 2:1 mux দিয়ে একটি 4:1 mux বানান। 2:1 mux-টিও (নাম mux2_1; input d0, d1, s; output y) নিজে লিখুন। তারপর একটি ছোট testbench-এর মূল loop লিখুন, যা sel-এর চারটি মান আর d-এর ১৬টি মান (মোট ৬৪টি) চালিয়ে y == d[sel] মেলায়।
    module mux2_1 (input wire d0, d1, s, output wire y);
        assign y = s ? d1 : d0;
    endmodule
    
    module mux4_s (input wire [3:0] d, input wire [1:0] sel, output wire y);
        wire lo, hi;
        mux2_1 m0 (.d0(d[0]), .d1(d[1]), .s(sel[0]), .y(lo));   // picks d0 or d1
        mux2_1 m1 (.d0(d[2]), .d1(d[3]), .s(sel[0]), .y(hi));   // picks d2 or d3
        mux2_1 m2 (.d0(lo),   .d1(hi),   .s(sel[1]), .y(y));    // picks lo or hi
    endmodule
    
    // testbench core (inside initial begin ... end; d, sel are reg; i, k, errors integer)
    for (i = 0; i < 16; i = i + 1)
        for (k = 0; k < 4; k = k + 1) begin
            d = i[3:0];  sel = k[1:0];
            #10;
            if (y !== d[sel]) errors = errors + 1;
        end

    যুক্তি: প্রথম স্তরের দুটি mux sel[0] দিয়ে জোড়ার ভেতর থেকে বাছে (d0/d1 আর d2/d3), দ্বিতীয় স্তরের mux sel[1] দিয়ে দুই জোড়ার মধ্যে বাছে। যেমন sel = 10: m0 → d[0], m1 → d[2], m2 (sel[1] = 1) → hi = d[2] ✓. Testbench-এ ১৬ × ৪ = ৬৪টি পরীক্ষা — সব input-এর exhaustive যাচাই; শেষে $display দিয়ে errors ছাপুন, প্রত্যাশিত মান 0.

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

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

ক্রমএকটি নকশা Verilog থেকে FPGA board-এ চালানো পর্যন্ত ধাপগুলো সঠিক ক্রমে সাজান (▲▼ চেপে সরান)

  1. Verilog-এ RTL নকশা লেখা
  2. Testbench লিখে simulation-এ যাচাই করা
  3. Synthesis করে netlist পাওয়া
  4. Place and route — LUT বসানো আর তার জোড়া
  5. Bitstream তৈরি করে FPGA-তে load করা
  6. Board-এ আসল সংকেত দিয়ে পরীক্ষা করা

একটি Verilog module-এ (n1 আর n2 আগেই wire হিসেবে ঘোষিত) পরপর তিনটি লাইন: assign y = n1 | n2;, assign n1 = a & b;, assign n2 = ~c;. কোন কথাটি সঠিক?

  • y সবসময় n1-এর পুরোনো মান ব্যবহার করবে, কারণ y-এর লাইন আগে লেখা
  • এটি compile error, কারণ n1-এ মান বসানোর আগেই y-এর লাইনে n1 ব্যবহার হয়েছে
  • লাইনের ক্রম বদলালেও একই circuit; y সবসময় (a·b) ∨ ¬c
  • তিনটি লাইন simulation শুরুতে একবার চলে, তারপর আর না

Verilog-এ a = 4'b1010 হলে {2{a[1:0]}}-এর মান কত?

  • 2'b10
  • 4'b1010
  • 4'b1000
  • 4'b0100 (10 + 10)

নিচের কোন combinational block থেকে synthesis একটি অনাকাঙ্ক্ষিত latch তৈরি করবে?

  • always @(*) if (en) q = d;
  • always @(*) begin q = 1'b0; if (en) q = d; end
  • always @(*) if (en) q = d; else q = 1'b0;
  • assign q = en ? d : 1'b0;

Verilog-এ reg হিসেবে ঘোষণা করা একটি সংকেত সম্পর্কে কোনটি সঠিক?

  • synthesis-এ সবসময় একটি flip-flop তৈরি হয়
  • এটিকে শুধু assign দিয়ে মান দেওয়া যায়
  • এর মান শুধু 0 বা 1 হতে পারে, x বা z নয়
  • এটি always/initial block-এ মান পায়; hardware কী হবে তা block-এর গঠন ঠিক করে

নিচের কোনটি একটি synthesizable নকশার (testbench নয়) ভেতরে লিখলে synthesis tool সাধারণত উপেক্ষা করে বা অসমর্থিত বলে?

  • for (i = 0; i < 8; i = i + 1) দিয়ে ৮টি বিটে একই কাজ
  • case (op) … endcase
  • assign #5 y = a & b;-এর #5 অংশ
  • assign y = {a, b};

একটি FPGA-র ৪-input LUT-এ কত বিট configuration মেমরি থাকে, আর এটি কোন function বানাতে পারে?

  • ৪ বিট; শুধু AND আর OR
  • ১৬ বিট; ৪ input-এর যেকোনো Boolean function
  • ৮ বিট; যেকোনো ৪-input function
  • ১৬ বিট; শুধু NAND, কারণ NAND universal

শব্দকোষ

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

মনে রাখুন

  1. HDL circuit বর্ণনা করে, instruction নয়। একই Verilog দুই কাজে: simulation (যাচাই) আর synthesis (gate বা LUT-এর netlist) — তারপর FPGA-র bitstream বা ASIC-এর layout.
  2. Hardware একসাথে চলে: প্রতিটি assign স্থায়ী সংযোগ, লাইনের ক্রম অর্থহীন; একটি পরিবর্তন শুধু নির্ভরশীল অংশ জাগায় — একে বলে event-driven simulation.
  3. মূল গঠন: module/port, wire (চালিত তার) বনাম reg (procedural variable — register নয়) বনাম SystemVerilog logic; [3:0] vector, 4'b1010/8'hFF, চার মান 0/1/x/z; reduction, concatenation, replication; ফলের মাপ বাঁ পাশ ঠিক করে।
  4. Structural (instance জুড়ে: half adder → full adder → ripple adder) আর behavioural (always @(*) + = + case: mux, decoder, 7-segment, ALU, parameter দিয়ে মাপ)। প্রতিটি পথে প্রতিটি output-কে মান না দিলে latch.
  5. Self-checking testbench (initial, #10, loop, !==, $display, $finish) ছাড়া নকশা অসম্পূর্ণ; delay আর $-function synthesizable নয়। Sequential-এর নিয়ম: always @(posedge clk) + <= — মডিউল ৩-এ।

পরের পর্বে: এতক্ষণ সব circuit শুধু এই মুহূর্তের input দেখেছে। এবার স্মৃতি — latch আর flip-flop: SR, D, JK, T, আর কেন আধুনিক নকশায় clock-এর ধারে মান নেওয়া (edge-triggering) এত গুরুত্বপূর্ণ। এই পর্বের latch-এর ফাঁদটি সেখানে আরও স্পষ্ট হবে।

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

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