1章 実習ボード、開発ツールの使い方

1.1 使用する実習ボード

本実習では Intel社(ALTERA社)のFPGAチップCyclone Vが搭載された DE0-CV開発ボードを使います。 このボードは、スライドスイッチ(SW9-0)、プッシュスイッチ(KEY0-4)、 LED(LEDR9-0)、7セグメントLED(HEX5-0)、GPIOなど多くのI/Oを 備えています。

DE0-CV

http://cd-de0-cv.terasic.com より、ユーザーマニュアルをダウンロードしておいてください。

1.2 開発ツールについて

本実習では回路設計を行うための EDA(electronic design automation) ツールとして Quartus Prime Lite Edition を利用します。

インテル Quartus Prime

1.3 実習ボード・開発ツールの使い方

実習ボードのスライドスイッチSW7-0に入力された 8 ビットの入力を そのままLEDのLEDR7-0に表示する回路の設計実装を行う実習を通して、 実習ボードと開発ツールの使い方を学んでいきましょう。

それでは、Quartus Prime Lite Editionを起動し、実習用のプロジェクトを作成します。

1.3.1 プロジェクトの作成

ウィンドウ左上の [File] > [New Project Wizard] より、プロジェクト作成のためのウィザードを起動します。 起動すると、プロジェクト作成の設定項目が示された Introduction の画面が開きます。

Introduction

[Next] を押して Project name and directory の設定にうつります。


Project name and directory

ProjectName

ここでは以下の3項目をします。

  • working directory (プロジェクトフォルダ)
  • project (プロジェクト名)
  • top-level entity (PFGAチップのPINを接続する一番外側の回路モジュール名)

working directory はプロジェクト用にフォルダを作成し、そのフォルダを選択します。 (パスには日本語やスペースを含まないようにすることをお勧めします)

設定例を示します。

  • working directory: Z:/digital/working_directory/simple_io
  • project: simple_io
  • top-level entity: simple_io

Project Type

ProjectType

ここでは Empty project を選択してください。 設定を行ったら [Next] を押します。

Add Files

AddFiles

既に作成したデザインファイル(HDLのソースコード)やライブラリを プロジェクトに取り込みたい場合に設定します。

今回はゼロから作成しますので、ファイルの追加は行いません。 設定を行ったら [Next] を押します。

Family, Device & Board Settings

DeviceSettings

開発対象のデバイス(FPGAチップ)を選択します。 実習ボード DE0-CV に搭載されている FPGA チップ Cyclone V 5CEBA4F23C7 を設定します。

右側の Name filter に 5CEBA4F23C7 と入力し、 下側の Available deices に表示される 5CEBA4F23C7 を選択しましょう。 設定を行ったら [Next] を押します。

EDA Tool Settings

EDAToolSetting

シミュレータなど他のEDAツールを使う場合の設定です。 今回はそのままで次に進んでください。 設定を行ったら [Next] を押します。

Summary

Summary

これまでの設定を確認します。 設定になければ[Finish]を押してください。 プロジェクトのひな型が作成されます。

1.3.2 デザインファイルの作成

それでは、System Verilog で回路モジュールを記述し、 そのファイルをプロジェクトに追加します。

左上の [File] > [New] を選択すると、 新しいファイルを選択するためのウィザード New が開きます。 ウィザードに表示された一覧から Design Files 下の System Verilog HDL File を選択し、 [OK] を押します。

中央のペインにテキストエディタが立ち上がります。 以下に示すデザインファイルを作成し、適切なファイル名を付けてプロジェクトフォルダの下に保存します。

simple_io.sv
module simple_io(
  input   logic [7:0] sw,
  output  logic [7:0] led
);

  assign led = sw;

endmodule

ファイルの保存は [File] > [save] を選択すると行えます。 なお、ファイル名の拡張子 System Verilog を示す .sv とする必要があります。(例 simple_io.sv)

SaveFile

1.3.3 プロジェクトのビルド

デザインファイルを追加したプロジェクトをビルドします。 以下の手順でビルドを行います。

  • デザインファイルの解析
  • FPGAのピンの割り当て
  • プロジェクトのコンパイル

デザインファイルの解析

[Processing] > [Start] > [Start Analysis & Elaboration] を選択し、デザインファイルの解析を行います。 数分程度時間がかかります。

下記の様に表示されれば大丈夫です。

Info: Quartus Prime Analysis & Elaboration was successful.

なお、デザインファイルに誤りがあると下記の様なエラー表示がでます。 修正を行ったうえで再度[Start Analysis & Elaboration]を行ってください。

Error (10161): Verilog HDL error at simple_io.sv(8): ...

FPGA のピンの設定

[Assignments] > [Pin Planner]を選択してPin Planner を起動します。 ここでは、設計した回路の入出力信号を FPGA のどのピンに割り当てるかを設定します。

今回は、simple_ioモジュールの出力信号 led[7]-led[0] をボードの LED デバイス LEDR7-LEDR0 に、 入力信号 sw[7]-sw[0] をスライドスイッチ SW7-sw0 に接続されるように、 表1.1のとおりピンの設定を行います。

表の Location はFPGAチップのピンを表します。

[表1.1 : simple_ioモジュールのピン割り当て]

Node NameLocation割り当てデバイス入出力
led[0]PIN_AA2LEDR0output
led[1]PIN_AA1LEDR1output
led[2]PIN_W2LEDR2output
led[3]PIN_Y3LEDR3output
led[4]PIN_N2LEDR4output
led[5]PIN_N1LEDR5output
led[6]PIN_U2LEDR6output
led[7]PIN_U1LEDR7output
sw[0]PIN_U13SW0input
sw[1]PIN_V13SW1input
sw[2]PIN_T13SW2input
sw[3]PIN_T12SW3input
sw[4]PIN_AA15SW4input
sw[5]PIN_AB15SW5input
sw[6]PIN_AA14SW6input
sw[7]PIN_AA13SW7input

pin planner の Location の欄に Node Name に対応する PIN 番号を設定してください。 (なお、Locationの欄に AA2 と入力すると PIN_AA2 と補完されます) 設定を終えたら、pin planner を閉じてください。

PinPlanner

なお、実習ボードDE0-CVにおいて、 PFAGの各ピンがそれぞれどのI/Oデバイスに接続されているかは DE0-CV ユーザーマニュアルを確認してください。

プロジェクトのコンパイル

[Processing]>[Start Compilation]を選択して、 プロジェクトのコンパイルを行います。

しばらく時間がかかります。下記の様に表示されるとコンパイル成功です。

Info (293000): Quartus Prime Full Compilation was successful.

1.3.4 回路情報の回路への書き込み

プロジェクトをビルドして作成された回路情報を実習ボードに書き込みます。

まず、PCと実習ボードDE0-CVをUSBケーブルで接続し、実習ボードの電源を入れます。 SW10 は RUN に設定しておいてください。

Quartus Prime で、[Tools] > [Programmer] を選択し Programmer を起動します。

Programmer

ウィンドウ上部では次を設定してください。

  • [Hardware Setup]でUSB-Blaster[USB-0]を選択
  • ModeはJTAGを選択

中央のペインでは、プロジェクトをビルドして作成された回路情報のsofファイル(.sof)を選択します。 空欄の場合は [Add File] より output_files/ 下にあるsofファイルを選び追加してください。 選択した sofファイルの Program/Configure にチェックを入れてください。

この状態で [Start] をクリックすると、書き込みが開始されます。 Progressが100%になると書き込み完了です。

1.3.5 動作確認

実習ボードのスライドスイッチ SW7-SW0 をそれぞれオン・オフすると、 対応する LEDR7-LED0 が点灯したり消灯したりするはずです。 動作を確認しましょう。

1.3.6 プロジェクトの保存

[File] > [Save Project] より、プロジェクトの保存ができます。

次回以降、保存したプロジェクトを利用したいときは [File] > [Open Project] からプロジェクト名の付いたqpfファイルを選択します。

A. 演習課題

新たなプロジェクトを作成し、リスト1.2に示すデザインファイルで設計される回路を実装しましょう。 ただし、プロジェクトのtop-level entity は bitwise_and とします。 また、ピンの割り当ては表1.2の通りとします。

この回路がどのような動作をするか、スライドスイッチsw7-0をいろいろと切り替え観察しましょう。

[リスト1.2 : bitwise_and.sv]

bitwise_and.sv
module bitwise_and(
  input   logic [3:0] sw_high,
  input   logic [3:0] sw_low,
  output  logic [3:0] led
);

  assign led = sw_high & sw_low;

endmodule

[表1.2 : bitwise_and のピンの割り当て]

Node NameLocation割り当てデバイス入出力
led[0]PIN_AA2LEDR0output
led[1]PIN_AA1LEDR1output
led[2]PIN_W2LEDR2output
led[3]PIN_Y3LEDR3output
sw_low[0]PIN_U13SW0input
sw_low[1]PIN_V13SW1input
sw_low[2]PIN_T13SW2input
sw_low[3]PIN_T12SW3input
sw_high[0]PIN_AA15SW4input
sw_high[1]PIN_AB15SW5input
sw_high[2]PIN_AA14SW6input
sw_high[3]PIN_AA13SW7input

2章 SystemVerilog 101

本章では、SystemVerilogの文法のうち、本実習での回路設計を行う上で必要最低限の基本部分を説明します。

2.1 module および assign文

SystemVerilogではモジュールを一つの単位として回路を記述していきます。 モジュールについての記述は、予約語の module と endmodule に囲まれます。

<リスト2.1 simple_ioモジュール>

simple_io.sv
module simple_io(         // (1) モジュール名: simple_io
  input   logic [7:0] a,  // (2) 8-bit logic 型の入力信号
  output  logic [7:0] y   // (3) 8-bit logic 型の出力信号
);
  // 回路記述
  assign y = a;           // (4)
endmodule

リスト2.1に簡単な回路モジュール記述を示しました。 図2.1に示すような回路が構成されます。

simple_io モジュール

<図2.1 simple_io モジュール で構成される回路>

module と入出力信号

回路モジュールを記述するときは、 まずモジュール名と入出力信号のリストを記述します。

sv
module simple_io(         // (1) モジュール名: simple_io
  input   logic [7:0] a,  // (2) 8-bit logic 型の入力信号
  output  logic [7:0] y   // (3) 8-bit logic 型の出力信号
);
  ...(中略)
endmodule

この例では、モジュール名 simple_io で、 モジュールの入力信号として 8-bit logic 型の入力 a 、 出力信号として 8-bit logic 型の出力 b があるような 回路モジュールを記述しています。

logic 型は 1-bit のデジタル信号の値を表す型で、 論理 0, 1と、ハイインピーダンス状態 z および不定値 x の 値を取ることができます。

8-bitの信号 a の各ビットは a[7], a[6], ..., a[0] のように記述できます。y についても同様です。 また、例えばa[7], a[6], a[5], a[4] をまとめて、a[7:4] のように記述することもできます。

assign 文

simple_io モジュールの内部では、assign 文を用いて (出力)信号 y に(入力)信号 a を割り当てています。

sv
  assign y = a; 

8-bit の信号線 y に同じく8-bit の信号線 a を各ビットの順序を保って結線しているとイメージするとよいでしょう。 この simple_io モジュールは 8-bit の入力 a をそのまま出力 y に出力する回路となっています。


2.2 ビット論理演算

<リスト2.2 logic_gates モジュール>

logic_gates.sv
module logic_gates(
  input  logic [1:0] a,
  input  logic [1:0] b,
  output logic [1:0] y1,
  output logic [1:0] y2,
  output logic [1:0] y3,
  output logic [1:0] y4,
  output logic [1:0] y5
);

  assign y1 = a & b;      // (1) bitwise AND
  assign y2 = a | b;      // bitwise OR
  assign y3 = a ^ b;      // bitwise XOR
  assign y4 = ~a;         // bitwise NOT
  assign y5 = ~(a & b);   // bitwise NAND

endmodule

リスト2.2に様々なビット論理演算を用いて構成した回路モジュール logic_gate を示しました。 このモジュールは 2 つの 2-bit 信号 a, b を入力信号とし、5つの 2-bit 信号 y1, y2, y3, y4, y5 を出力信号とします。 y1, y2, y3 にはそれぞれ a と b のビットごとのAND, OR, XOR をとったものが出力されます。 y4 には a のビットごとのNOT、y5 には a と b のビットごとのNANDが出力されます。

リスト2.3中の(1)の部分は下記のようにビットごとに割り当てを行っても同じです。

<リスト2.4 ビットごとの割り当て>

  // リスト2.3の(1)は下記のように書いてもよい
  assign y1[1] = a[1] & b[1];
  assign y1[0] = a[0] & b[0];

演習

リスト2.2 の logic_gates モジュールを実習ボード DE0-CV に実装してその動作を確認しましょう。(Top-Level Entity を logic_gatesとします)

logic_gates モジュールの入出力信号は表2.2aのように DE0-CV の入出力デバイスに割り当てましょう。

<表2.2a logic_gates モジュールの入出力のデバイスへの割り当て>

信号名割り当てデバイス入出力
a[1:0]SW3-SW2input
b[1:0]SW1-SW0input
y1[1:0]LEDR9-LEDR8output
y2[1:0]LEDR7-LEDR6output
y3[1:0]LEDR5-LEDR4output
y4[1:0]LEDR3-LEDR2output
y5[1:0]LEDR1-LEDR0output

なお、pin planner での設定では、 以下のようなピンの割り当てを示した表が必要となります。

<表2.2b logic_gates モジュールのピン割り当て>

Node NameLocation
a[0]PIN_T13
a[1]PIN_T12
y1[0]PIN_L2
......

DE0-CVのユーザーマニュアルから、割り当てデバイスと FPGAのピン番号(Location) との対応を確認して、 この表を作成し、 pin planner で設定しましょう。


2.3 定数リテラル

定数リテラルはリスト2.3のようにビット幅と基数(2進、8進、10進、16進)を指定して記述します。 定数を用いるときは、原則ビット幅を指定するようにしましょう。

  // 3-bit 幅の定数 5(10進) の表示方法
  3'b101        // 3-bit 2進数表示
  3'o5          // 3-bit 8進数表示
  3'd5          // 3-bit 10進数表示
  3'h5          // 3-bit 16進数表示

  // 8-bit 幅の定数 172(10進) の表示方法
  8'b1010_1100; // 2進数表示
  8'o254        // 8進数表示
  8'd172        // 10進数表示
  8'hAC         // 16進数表示

なお、アンダースコア(_)は無視されますので、 桁が長い数の場合は、適宜挿入するとよいでしょう。

リスト2.3 に定数を利用した回路モジュールを示します。

<リスト2.3 mask_C3 モジュール>

mask_C3.sv
module mask_C3 (
  input  logic [7:0] a,
  output logic [7:0] y
);
  assign y = a & 8'b1100_0011;
  // assign y = a & 8'hC3 と書いても同じ
endmodule

演習

リスト2.3a の mask_C3 モジュールを実習ボード DE0-CV に実装してその動作を確認しましょう。

mask_C3 モジュールの入出力信号は表 2.3a のように DE0-CV の入出力デバイスに割り当てましょう。

<表2.3a mask_C3 モジュールの入出力のデバイスへの割り当て>

信号名割り当てデバイス入出力
a[7:0]SW7-SW0input
y[7:0]LEDR7-LEDR0output

2.4 条件割り当て文

リスト2.4 の mux モジュールは図 2.4 のようなマルチプレクサを与えます。

<リスト2.7 mux モジュール>

mux.sv
module mux(
  input   logic       sel,
  input   logic [3:0] d0,
  input   logic [3:0] d1,
  output  logic [3:0] y
);

  assign y = (sel == 1'd1) ? d1 : d0;
endmodule

マルチプレクサ

<図2.4 mux で与えられるマルチプレクサ>

mux4 モジュールは1-bitの信号 sel が 1 の時は y に d1 を出力し、そうでない場合は y に d0 を出力します。

リスト2.7では条件割り当て文を利用しています。

条件割り当て文の構文
 condition ? value_for_true : value_for_false

条件部分 condition には相等比較(==, !=)や大小比較(<, <=, >, >=)を使うことができます。 条件が成り立つときに返す値を value_for_true に、成り立たない場合に返す値を value_for_false に指定します。

演習

リスト 2.4 の mux モジュールを実習ボード DE0-CV に実装してその動作を確認しましょう。

mux モジュールの入出力信号は表2.4のように DE0-CV の入出力デバイスに割り当てましょう。

<表2.4 mux モジュールの入出力のデバイスへの割り当て>

信号名割り当てデバイス入出力
selSW9input
d1[3:0]SW7-SW4input
d0[3:0]SW3-SW0input
y[3:0]LEDR3-LEDR0output

2.5 ビット連接

リスト2.5にビット連接を使った回路モジュールを示します。

<リスト2.5 bitmix モジュール>

bitmix.sv
module bitmix(
  input   logic [3:0] a,
  input   logic [3:0] b,
  output  logic [7:0] y
);

  assign y = {a[1:0], b, a[3:2]}; // (1)

endmodule

リスト中(1)の右辺にあるように、信号を{}でくくることで複数の信号のビット連接を行うことができます。 下記リスト2.5aのように書いたものと同じ働きとなります。

<リスト2.5a ビットごとの割り当て>

  assign y[7] = a[1];
  assign y[6] = a[0];
  assign y[5] = b[3];
  assign y[4] = b[2];
  assign y[3] = b[1];
  assign y[2] = b[0];
  assign y[1] = a[3];
  assign y[0] = a[2];

演習

リスト2.5 の bitmix モジュールを実習ボード DE0-CV に実装してその動作を確認しましょう。

bitmix モジュールの入出力信号は表2.5 のように DE0-CV の入出力デバイスに割り当てましょう。

<表2.5 bitmix モジュールの入出力のデバイスへの割り当て>

信号名割り当てデバイス入出力
a[3:0]SW7-SW4input
b[3:0]SW3-SW0input
y[7:0]LEDR7-LEDR0output

2.6 算術演算(加減算)

加算回路や減算回路を構築する際は +- 演算子を用いて記述することができます。

<リスト2.6 adder モジュール(4ビット加算器)>

adder.sv
module adder(
  input   logic [3:0] a,
  input   logic [3:0] b,
  output  logic [3:0] sum,
  output  logic       carry
);

  assign {carry, sum} = a + b; // (1)

endmodule

リスト2.6 のadderモジュールは 4-bit の信号 a と b を加算し、その加算結果を 4-bit のsum に出力し、繰り上がりを 1-bit の carry に出力します。

リスト中(1)のように、加算は + 演算子を用いて書くことができます。同様に減算は - 演算子を使って記述できます。

演習

リスト2.6 の adder モジュールを実習ボード DE0-CV に実装してその動作を確認しましょう。

adder モジュールの入出力信号は表2.4のように DE0-CV の入出力デバイスに割り当てましょう。

<表2.6 adder モジュールの入出力のデバイスへの割り当て>

信号名割り当てデバイス入出力
a[3:0]SW7-SW4input
b[3:0]SW3-SW0input
carryLEDR9output
sum[3:0]LEDR3-LEDR0output

2.7 モジュール内部の信号

図2.7 の notAandBモジュールには入力信号 a, b と出力信号 y がありますが、 加えてモジュール内部に入力 a の not をとった信号を保持する信号線 notA が存在しています。 この様なモジュール内部で必要な信号線は、 リスト2.11の(1)のようにモジュール内で宣言して使用することができます。

notAandB モジュール

<図2.7 モジュール内部に信号線 notA を持つ notAandB モジュール>

<リスト2.7 notAandB モジュール>

notAandB.sv
module notAandB (
  input logic a,
  input logic b,
  output logic y
);
  // (1) モジュール内部の信号 notA を宣言
  logic notA;

  assign notA = ~a;
  assign y = notA & b;

endmodule

演習

リスト2.7の notAandB モジュールを実習ボード DE0-CV に実装してその動作を確認しましょう。

notAandB モジュールの入出力信号は表2.7のように DE0-CV の入出力デバイスに割り当てましょう。

<表2.7 notAandB モジュールの入出力のデバイスへの割り当て>

信号名割り当てデバイス入出力
aSW1input
bSW0input
yLEDR0output

2.8 always文

レジスタなどの順序回路や、エンコーダ・デコーダなどの複雑な組み合わせ回路は always 文(always_ff 文、always_comb 文など)を使って設計できます。 always 文を使った回路設計については、次章以降で説明します。

3章 組み合わせ論理回路の設計

本章では、always_comb 文を用いて加算器やエンコーダ、マルチプレクサなどの組み合わせ論理回路を設計する方法を学びます。

always_comb 文はいずれかの信号に変化があったときに起動し、always_comb 文の内部の処理が実行されるような回路が構成されます。 これによって組合せ回路を設計することができます。

3.1 4ビット加算器

4ビット加算器 adder

<図 3.1 4ビット加算器 adder>

2章では図 3.1 に示す4ビット加算器を、算術演算子 + と assign 文を用いて設計しました(リスト 2.6)。

同じ回路を以下のリスト 3.1 のように always_comb 文を用いて記述することもできます。

<リスト3.1 adder モジュール(4ビット加算器)>

adder.sv
module adder(
  input   logic [3:0] a,
  input   logic [3:0] b,
  output  logic [3:0] sum,
  output  logic       carry
);

  always_comb begin
    {carry, sum} = a + b;
  end

endmodule

この例では、信号 a もしくは b に変化があったときに always_comb 文が起動し、 a と b の加算結果を sum に出力し、その繰り上がりを carry に出力しています。 すなわち curry 付きの加算回路が実現されます。

always_comb 文の内部で信号の代入を行うには、ブロッキング代入 = を用います。 ノンブロッキング代入と呼ばれる <= を使うこともできますが、 両者には評価方法についての挙動の違いがあり、 always_comb 文で組み合わせ回路を設計する場合には、 原則としてブロッキング代入 = を用いることを推奨します。

なお、always_comb 文や、次章以降で学ぶ always_ff 文などの always 文の内部では assign 文による代入を行うことはできません。 また、always 文の外部ではブロッキング代入やノンブロッキング代入を行うことはできません。

演習

リスト3.1の adder モジュールを実習ボード DE0-CV に実装してその動作を確認しましょう。 adder モジュールの入出力信号は表3.1のように DE0-CV の入出力デバイスに割り当ててください。

<表3.1 adder モジュールの入出力のデバイスへの割り当て>

信号名割り当てデバイス入出力
a[3:0]SW7-SW4input
b[3:0]SW3-SW0input
carryLEDR9output
sum[3:0]LEDR3-LEDR0output

3.2 7セグメントデコーダ

DE0-CV には 7 セグメント LED ディスプレイが 6 個 (HEX0~HEX5) 搭載されています。 それぞれのディスプレイは 7 個の LED を用いて数字やアルファベットを表示することができます。

7セグメント LED ディスプレイ (HEX0)

7セグメント LED ディスプレイの各 LED は、対応する入力端子に 0 を入力すると点灯し、 1 を入力すると消灯します。 例えば HEX0 の場合、HEX06~HEX00 に 0110000 を入力すると 番号 6, 3, 2, 1, 0 のLEDが点灯し "3" のパターンを表示できます。

では、DE0-CV に搭載されている 7 セグメント LED ディスプレイを用いて、 4-bit の入力信号を16進数のパターン (0,1,2,3,4,5,6,7,8,9,A,b,c,d,E,F) に変換して表示する回路を設計します。

この回路は、4-bit の入力信号 num[3:0] と 7-bit の出力信号 hex[6:0] の関係が、 表3.2の真理値表で与えられる組み合わせ論理回路 sseg_decoder (図3.2)によって実現できます。

<表3.2 sseg_decoderモジュールの真理値表>

入力 num[3:0]出力 hex[6:0]表示パターン
000010000000
000111110011
001001001002
001101100003
010000110014
010100100105
011000000106
011110110007
100000000008
100100100009
10100001000A
10110000011b
11000100111c
11010100001d
11100000110E
11110001110F

7セグメントデコーダ <図3.2 7セグメントデコーダ sseg_decoder>

このような回路の機能が真理値表で与えられる場合は、case 文を用いて設計することができます。 case 文は入力信号の値に応じて出力信号の値を決定するための文です。 case 文は always 文の中で使用します。

リスト3.2に sseg_decoder モジュールの記述例を示します。

<リスト3.2 sseg_decoder モジュール(7セグメントデコーダ)>

sseg_decoder.sv
module sseg_decoder(
  input   logic [3:0]   num,
  output  logic [6:0]   hex
);

  always_comb begin
    case (num)
      4'h0  : hex = 7'b100_0000;
      4'h1  : hex = 7'b111_1001;
      4'h2  : hex = 7'b010_0100;
      4'h3  : hex = 7'b011_0000;
      4'h4  : hex = 7'b001_1001;
      4'h5  : hex = 7'b001_0010;
      4'h6  : hex = 7'b000_0010;
      4'h7  : hex = 7'b101_1000;
      4'h8  : hex = 7'b000_0000;
      4'h9  : hex = 7'b001_0000;
      4'ha  : hex = 7'b000_1000;
      4'hb  : hex = 7'b000_0011;
      4'hc  : hex = 7'b100_0110;
      4'hd  : hex = 7'b010_0001;
      4'he  : hex = 7'b000_0110;
      4'hf  : hex = 7'b000_1110;
      default  : hex = 7'b111_1111; 
      // 上記で全パターン尽くされているのでこのdefaultは実際は不要
    endcase
  end

endmodule

表 3.2 の真理値表がそのまま case 文の中に示されています。 上記の case 文では、信号 num の値について上からパターンマッチが行われ、 パターンが一致するところの文が実行されます。 例えば、num = 4'b1010 (= 4'hA)の時は、hex に 7'b0001000 が代入されます。

case 文を用いて組み合わせ論理回路を設計する場合は、すべての入力パターンを尽くすように記述しなければいけません。 必要に応じて default 文を用い、すべての入力パターンが尽くされるようにしましょう。

演習

リスト 3.2 の sseg_decoder モジュールを実習ボード DE0-CV に実装してその動作を確認しましょう。 sseg_decoder モジュールの入出力信号は表 3.2 のように DE0-CV の入出力デバイスに割り当ててください。

<表 3.2 sseg_decoder モジュールの入出力のデバイスへの割り当て>

信号名割り当てデバイス入出力
num[3:0]SW3-SW0input
hex[6:0]HEX06-HEX00output

3.3 マルチプレクサ

図3.3に示す4入力マルチプレクサを設計することを考えます。 選択信号 sel の値によって 4 つの入力信号 d0, d1, d2, d3 のいずれか一つが選ばれ、 その信号が出力されます。

4入力マルチプレクサ

<図 3.3 4入力マルチプレクサ mux4>

表 3.3 に 4入力マルチプレクサ mux4 の機能表を示します。

<表3.3 mux4 モジュールの機能表>

選択信号 sel出力 y
00d0
01d1
10d2
11d3

mux4 回路は表3.3に従って選択信号の値により出力 y の値が切り替わるので 先ほどと同様に case 文を用いて記述することができます。 リスト 3.3 に mux4 モジュールの記述例を示します。

<リスト 3.3 mux4 モジュール>

mux4.sv
module mux4 (
  input   logic [1:0] sel, // 選択信号
  input   logic [1:0] d0,
  input   logic [1:0] d1,
  input   logic [1:0] d2,
  input   logic [1:0] d3,
  output  logic [1:0] y
);

  always_comb begin
    case (sel)
      2'b00   : y = d0;
      2'b01   : y = d1;
      2'b10   : y = d2;
      2'b11   : y = d3;
      default : y = 2'b00; 
    endcase
  end

endmodule

演習

リスト3.3の mux4 モジュールを実習ボード DE0-CV に実装してその動作を確認しましょう。 mux4 モジュールの入出力信号は表3.3aのように DE0-CV の入出力デバイスに割り当ててください。

<表3.3a mux4 モジュールの入出力のデバイスへの割り当て>

信号名割り当てデバイス入出力
sel[1:0]SW9-SW8input
d0[1:0]SW7-SW6input
d1[1:0]SW5-SW4input
d2[1:0]SW3-SW2input
d3[1:0]SW1-SW0input
y[1:0]LEDR1-LEDR0output

3.4 プライオリティエンコーダ

図3.4に示すような 4 入力のプライオリティエンコーダ priotiry_encoder を設計します。

プライオリティエンコーダ

このエンコーダは 4 つの各 1 ビットの入力信号 d[3], d[2], d[1], d[0] のうち、 1 が入力されている最も上位のビット位置を 2 進数表示で y に出力します。 出力 en は入力が有効かどうかを示す信号で、 d[3] ~ d[0] のいずれかに 1 が入力されているときには 1 を出力し、 どれにも 1 が入力されていない(つまりすべて 0 が入力されている)ときは 0 を出力します。 例えば d[3:0] として 0101 が入力されたときは y = 10, en = 1 が出力されます。

表3.4にこの 4 入力のプライオリティエンコーダ priority_encoder の機能表を示します。

<表 3.4 priority_decoder モジュールの機能表 >

入力 d[3:0]出力 y[1:0]出力 en
1***111
01**101
001*011
0001001
上記以外000

表の入力 d[3:0] の欄での * は、そのビット位置の信号が 0, 1 どちらの場合でも 対応する行で出力が決定されることを示しています。

このような回路は casez 文を用いると簡単に記述することができます。

priority_encoder.sv
module priority_encoder(
  input   logic [3:0] d,  // 入力信号
  output  logic [1:0] y,  // 出力信号
  output  logic       en  // 有効信号
);

  assign en = |d; // d[3] | d[2] | d[1] | d[0] と同じ
  
  always_comb begin
    casez (d)
      4'b1??? : y = 2'b11;
      4'b01?? : y = 2'b10;
      4'b001? : y = 2'b01;
      4'b0001 : y = 2'b00;
      default : y = 2'b00;
     endcase
  end
  
endmodule

このコードでは casez 文で d の値でパターンマッチングを行っていますが、 機能表 3.4 において * で表した部分は 0 にも 1 にも対応するワールドカードとして ? を用いてパターンを記述しています。

出力 en への assign 文においては、 信号 d の 4 ビットすべて OR をとるリダクション演算子による記法 |d を用いています。 これは d[3] | d[2] | d[1] | d[0] と同じ意味になります。 なお、すべての AND をとりたいときは &d と記述します。

演習

リスト 3.4 の を実習ボード DE0-CV に実装してその動作を確認しましょう。 priority_encoder モジュール入出力信号は表 3.4a のように DE0-CV の入出力デバイスに割り当ててください。

<表3.4a priority_encoder モジュールの入出力のデバイスへの割り当て>

信号名割り当てデバイス入出力
d[3:0]SW3-SW0input
y[1:0]LEDR1-LEDR0output
enLEDR9output

4 章 構造モデル化による回路設計

System Verilog ではモジュールを組み合わせ、より大規模な回路モジュールを構築することができます。 本章では、これまでに設計した様々な回路モジュールを組み合わせて新しい回路を設計する方法を学びます。

4.1 7 セグメント表示付きプライオリティエンコーダ

3 章ではプライオリティエンコーダ (priority_encoder) や 7 セグメントデコーダ (sseg_decoder) を作成しました。 ここでは、プライオリティエンコーダへの入力と出力とをそれぞれ 7 セグメントデコーダを用いて変換し、16進数表示のパターンとして 7 セグメント LED 上で表示する回路を設計します。

図 4.1a のように、priority_encoder を 1 個と sseg_decoder を 2 個組み合わせてより大きな回路モジュール shell を設計していきます。

7セグメント表示付きプライオリティエンコーダ

<図 4.1a 7セグメント表示付きプライオリティエンコーダ>

外側の shell モジュールは、入力として 4 ビットの信号 SW があり、 出力としては、1 ビットの信号 LEDR0 と 7 ビットの信号 HEX1, HEX0 があります。 ここでは、これらの入出力信号について、入力の SW は実習ボードのスライドスイッチへ接続し、 出力の LEDR0 は実習ボードの LED へ、HEX1 と HEX0 は 7 セグメント LED ディスプレイへ接続することを想定しています。

なお、Quartus Prime では回路を構成するモジュールのうち一番外側で FPGA の入出力に直接つながる回路モジュールの名前を プロジェクトの Top level design entity の名前にする必要があります。

それでは、Quartus Prime で Top level design entity を shell と設定したプロジェクトを作成しましょう。

プロジェクトを作成したら、リスト 4.1a に示した shell モジュールを定義したファイルをプロジェクトに登録してください。 なお、ここでは shell モジュールの入出力の定義だけを記述しています。モジュールの内部の記述は後程行います。

<リスト4.1a shell モジュール (作成途中版) >

shell.sv
module shell(
  input   logic [3:0] SW,
  output  logic [6:0] HEX0,
  output  logic [6:0] HEX1,
  output  logic       LEDR0
);
  // 回路の中身は後で作成 
endmodule

shell モジュールの内部では、priority_encoder モジュールと sseg_decoder モジュールを呼び出して使用します。 したがって、これらのモジュールを定義したファイルも必要となりますので、プロジェクトに追加して登録してください。 (プロジェクトには shell.sv, priotiry_encode.sv, sseg_decoder.sv のファイルが登録されていることを確認してください)

<リスト4.1b priority_encoder モジュール (再掲) >

priority_encoder.sv

priority_encoder.sv
module priority_encoder(
  input   logic [3:0] d,
  output  logic [1:0] y,
  output  logic       en
);

  assign en = |d; 
  
  always_comb begin
    casez (d)
      4'b1??? : y = 2'b11;
      4'b01?? : y = 2'b10;
      4'b001? : y = 2'b01;
      4'b0001 : y = 2'b00;
      default : y = 2'b00;
     endcase
  end
  
endmodule

<リスト4.1c sseg_decoder モジュール (再掲) >

sseg_decoder.sv

sseg_decoder.sv
module sseg_decoder(
  input   logic [3:0]   num,
  output  logic [6:0]   hex
);

  always_comb begin
    case (num)
      4'h0  : hex = 7'b100_0000;
      4'h1  : hex = 7'b111_1001;
      4'h2  : hex = 7'b010_0100;
      4'h3  : hex = 7'b011_0000;
      4'h4  : hex = 7'b001_1001;
      4'h5  : hex = 7'b001_0010;
      4'h6  : hex = 7'b000_0010;
      4'h7  : hex = 7'b101_1000;
      4'h8  : hex = 7'b000_0000;
      4'h9  : hex = 7'b001_0000;
      4'ha  : hex = 7'b000_1000;
      4'hb  : hex = 7'b000_0011;
      4'hc  : hex = 7'b100_0110;
      4'hd  : hex = 7'b010_0001;
      4'he  : hex = 7'b000_0110;
      4'hf  : hex = 7'b000_1110;
      default  : hex = 7'b111_1111; 
    endcase
  end

endmodule

図 4.1a をもとにすると shell モジュールの内部はリスト 4.1d のように記述することができます。

<リスト4.1d shell モジュール (完成版) >

shell.sv
module shell(
  input   logic [3:0] SW,
  output  logic [6:0] HEX0,
  output  logic [6:0] HEX1,
  output  logic       LEDR0
);

  logic [1:0] encoded;
  
  priority_encoder p_enc(
    .d  (SW),
    .y  (encoded),
    .en (LEDR0)
  );
  
  sseg_decoder dec1(
    .num  ({2'b00, encoded}),
    .hex  (HEX1)
  );
  
  sseg_decoder dec0(
    .num  (SW),
    .hex  (HEX0)
  );

endmodule

shell モジュールの内部では、モジュール内部の 2 ビットの信号 encoded を宣言したのち、 priority_encoder モジュールを呼び出しています。このモジュール呼び出しの部分を抜き出してコードの説明をします。

  priority_encoder p_enc(
    .d  (SW),
    .y  (encoded),
    .en (LEDR0)
  );

priority_encoder のようにすでに定義しているモジュールを呼び出すときは モジュール名の後にインスタンス名を付けて呼び出します。 ここでは、p_enc というインスタンス名を付けて priority_encoder モジュールを呼び出しています。 インスタンス名は(System Verilog の命名規則に沿っている限りは)好きにつけることができます。 なお、このようなモジュールの呼び出しをモジュールのインスタンス化といいます。

インスタンス名の後ろでは ( ) の中に、呼び出されたモジュール (ここでは priority_encoder モジュール) の 各入出力信号に、呼び出し側のモジュール (ここでは shell モジュール) のどの信号を割り当てるのかを記述していきます。 このコード片では、priority_encoder モジュールの入力信号 d に、shell モジュールの入力信号 SW を接続し、 priority_encoder モジュールの出力信号 y と en にそれぞれ、 shell モジュールの内部信号 encoded と 出力信号 LEDR を接続していることを記述しています。

図で示すと、このコード片では下の図において、緑色で示した priority_encoder をインスタンス化し、 赤色で示した部分の信号の接続を行ったことになります。

p_encインスタンス

shell モジュールのコードの次の部分では、sseg_decoder を dec1 というインスタンス名を付けてインスタンス化しています。

  sseg_decoder dec1(
    .num  ({2'b00, encoded}),
    .hex  (HEX1)
  );

sseg_decoder モジュールの入力信号 num には、shell モジュールの内部信号 encoded を接続していますが、 num は 4 ビットの信号なのに対して encoded は 2 ビットなので、ビット連接により上位 2 ビットに 0 を加えた信号 {2'b00, encoded} を接続しています。 sseg_decoder モジュールの出力信号 hex は shell モジュールの HEX1 に接続しています。

こちらも図で示すと、下図において、緑色で示した sseg_decoder をインスタンス化し、 赤色で示した部分の信号の接続を行ったことになります。

dec1インスタンス

shell モジュールのコードの最後の部分では、もう一つ ssed_decoder をインスタンス化しています。 インスタンス名は dec0 としています。

  sseg_decoder dec0(
    .num  (SW),
    .hex  (HEX0)
  );

このように同じモジュールを複数回呼び出すことが可能です。 ただし、それぞれの呼び出しでインスタンス名を変えて区別できるようにする必要があります。

演習

セグメント表示付きプライオリティエンコーダを実習ボード DE0-CV に実装してその動作を確認しましょう。 shell モジュールを Top level design entity として その入出力の信号を実習ボードのデバイスへ表 4.1 のように割り当ててください。

なお、Quartus Prime のプロジェクトを作成する際は、Top level design entity として shell モジュールを指定してください また、shell モジュールに加え、priority_encoder モジュールと sseg_decoder モジュールを定義しているファイルもプロジェクトに登録しておく必要があります。注意してください。

<表4.1 shell モジュールの入出力のデバイスへの割り当て>

信号名割り当てデバイス入出力
SW[3:0]SW3-SW0input
HEX0[6:0]HEX06-HEX00output
HEX1[6:0]HEX16-HEX10output
LEDR0LEDR0output

4.2 課題 (入出力表示付き 4 ビット加算器)

4ビット加算器 adder モジュールと 7 セグメントデコーダ sseg_decoder モジュールを組み合わせて、 以下に仕様を示すような、入出力表示付き 4 ビット加算器を設計し、ボードに実装してその動作を確認してください。

回路の仕様

入力部:

使用デバイス説明
SW7-SW44 ビットの加算器の入力 A
SW3-SW04 ビットの加算器の入力 B

出力部:

使用デバイス説明
HEX3, HEX2入力AとBの和を2桁の16進数で表示
HEX1入力Aの値を16進数で表示
HEX0入力Bの値を16進数で表示

回路の動作:

  • スライドスイッチ SW7-SW4 より 4 ビットの値 A を入力し、SW3-SW0 より 4 ビットの値 B を入力します。
  • 7セグメント LED の HEX1 には A の値を、HEX0 には B の値をそれぞれ 16 進数のパターンを表示します。
  • 7セグメント LED HEX3 と HEX2 には A + B の結果を 16 進数 2 桁で表示します。

回路の動作例

  • A = 0001, B = 1010 を入力した場合 :

    • HEX3, HEX2 : 0 b のパターンを表示
    • HEX1 : 1 のパターンを表示
    • HEX0 : A のパターンを表示
  • A = 1111, B = 0011 を入力した場合 :

    • HEX3, HEX2 : 1 2 のパターンを表示
    • HEX1 : F のパターンを表示
    • HEX0 : 3 のパターンを表示

5章 レジスタの設計

本章では、always_ff 文を用いてレジスタを設計する方法を学びます。 always_ff 文は、クロック信号の立ち上がりや立ち下がりなど、特定のタイミングで動作する回路を記述できます。 この文を使用することで、フリップフロップの動作を簡潔に記述でき、レジスタやカウンタなどの同期回路を設計することが可能になります。

5.1 4ビットレジスタ

図 5.1a に示すような 4 ビットのレジスタを設計します。 このレジスタはクロック信号 clock の立ち上がりのタイミングで、 4ビットの入力信号 d[3:0] を取り込んで 4 ビットの出力信号 q[3:0] に出力し、 その値を次の clock の立ち上がりが入ってくるまで、そのまま保持する回路です。

register4

<図 5.1a 4ビットレジスタ>

このような 4 ビットレジスタは、リスト 5.1 のように always_ff を用いて記述できます。

<リスト 5.1 register4 モジュール>

register4.sv
module register4(
  input   logic         clock,
  input   logic   [3:0] d,
  output  logic   [3:0] q
);

  always_ff @ (posedge clock) begin  // clock の立ち上がりで動作
    q <= d;   // d の値を q に代入 (ノンブロッキング代入)
  end
endmodule

always_ff 文の部分を抜き出すと以下のようになります。

always_ff @ (posedge clock) begin // clock の立ち上がりで動作
  q <= d;   // d の値を q に代入 (ノンブロッキング代入)
end

この always_ff 文では posedge clock というイベントが発生したときに、q <= d; の処理が実行されます。 posedge clock は、clock 信号の立ち上がりエッジを意味します。 q <= d; は、d の値を q に代入する処理です。 すなわち、clock 信号が 0 から 1 に変化したときに、always_ff 文が起動し qd の値が代入されます。

posedge clock の代わりに negedge clock を使用すると、clock 信号の立ち下がりエッジで動作することになります。

なお、q <= d; の形の代入はノンブロッキング代入と呼ばれ、d の値を q に代入することを意味します。 ノンブロッキング代入は、複数の代入が同時に実行される場合でも、各文が独立して動作することを保証します。 always_ff 文による同期式回路の設計ではノンブロッキング代入を使用することが推奨されます。

この 4 ビットレジスタの動作例をタイムチャートで示すと図 5.1b のようになります。

register4_timing

<図 5.1b 4ビットレジスタ register4 モジュールの動作例>

この4ビットレジスタ register4 モジュールを呼び出して使うには次のように記述します。

shell.sv
module shell(
  input   logic       KEY0,   // clock
  input   logic [3:0] SW,     // d[3:0]
  output  logic [3:0] LEDR    // q[3:0]
);

  register4 reg4(
    .clock (KEY0),
    .d     (SW),
    .q     (LEDR)
  );
endmodule

演習

4ビットレジスタを実習ボード DE0-CV に実装して、その動作を確かめてみましょう。 上記の shell モジュールをプロジェクトの Top level design entity として設定し、 その入出力を表 5.1 に示すように割り当ててください。

<表 5.1 shell モジュールの入出力のデバイスへの割り当て>

信号名割り当てデバイス入出力
KEY0KEY0input
SW[3:0]SW3-SW0input
LEDR[3:0]LEDR3-LEDR0output

ここで clock 信号を入力するために使用しているプッシュスイッチの KEY0 は、 ボタンを押し下げている間は 0 となり、離すと 1 となります。 KEY1 から KEY3 のプッシュスイッチについても同様です。 また、プッシュスイッチ KEY0 から KEY3 にはチャタリング防止のための回路が組み込まれています。 (なお、スライドスイッチ SW9 ~ SW0 にはチャタリング防止回路はついていません。)

レジスタの出力が変化するのは、KEY0 を押し下げた時なのか、それとも離した時なのか、そのタイミングを注意深く観察してください。


5.2 レジスタ (ビット幅のパラメータ化)

リスト 5.2 の register モジュールは、ビット幅をパラメータとして指定できるようにしたレジスタの設計例です。

<リスト 5.2 register モジュール>

register.sv
module register #(parameter WIDTH = 4)( // パラメータ WIDTH の定義 デフォルト値は 4
  input   logic             clock,
  input   logic [WIDTH-1:0] d,      // パラメータ WIDTH によるビット幅の指定 
  output  logic [WIDTH-1:0] q       // パラメータ WIDTH によるビット幅の指定
);

  always_ff @ (posedge clock) begin
    q <= d;
  end
endmodule

モジュールの定義において、モジュール名 register の後に #(parameter WIDTH = 4) と記述することで、 WIDTH というパラメータとそのデフォルト値(ここでは 4 )を設定しています。 モジュールの入出力信号の定義部分では、パラメータ WIDTH を使用して、 入力 d と出力 q のビット幅を指定しています。 このようにパラメータを用いると、モジュールを呼び出す際にビット幅を変更することができます。

モジュールを呼び出す際(インスタンス化)には、次のようにモジュールにパラメータの値を指定します。

shell.sv
module shell(
  input   logic       KEY0,
  input   logic [7:0] SW,
  output  logic [7:0] LEDR
);

  register #(.WIDTH(8)) reg8(   // パラメータ WIDTH を 8 に指定
    .clock  (KEY0),
    .d      (SW),
    .q      (LEDR)
  ); 
endmodule

ここでは register モジュールを呼び出す際に #(.WIDTH(8)) と記述することで、パラメータ WIDTH の値を 8 に指定しています。 これにより、register モジュールの入力 d と出力 q のビット幅がそれぞれ 8 ビットになり、8 ビットのレジスタを作成することができます。

演習

上記の 8 ビットレジスタを搭載した shell モジュールを実習ボード DE0-CV に実装して、 その動作を確かめてみましょう。 shell モジュールをプロジェクトの Top level design entity として設定し、 その入出力を表 5.2 に示すようにデバイスへ割り当ててください。

<表 5.2 shell モジュールの入出力のデバイスへの割り当て>

信号名割り当てデバイス入出力
KEY0KEY0input
SW[7:0]SW7-SW0input
LEDR[7:0]LEDR7-LEDR0output

5.3 同期リセット付きレジスタ

リスト 5.3 の register_r モジュールは、同期リセット機能を持つレジスタの設計例です。

<リスト 5.3 register_r モジュール>

register_r.sv
module register_r #(
  parameter WIDTH = 4,  // 入出力のビット幅 (デフォルトは 4)
  parameter logic [WIDTH-1:0] RESET_VALUE = '0 // リセット時の値 (デフォルトは 0)  
)(
  input   logic             clock,
  input   logic             reset, // reset
  input   logic [WIDTH-1:0] d,
  output  logic [WIDTH-1:0] q
);

  always_ff @ (posedge clock) begin
    if (reset == 1'b1) begin
      q <= RESET_VALUE; // reset が 1 のときは q にリセット値を設定
    end else begin 
      q <= d;           // reset が 0 のときは q に d の値を設定
    end 
  end

endmodule

この register_r モジュールは、2つのパラメータ WIDTHRESET_VALUE を持ちます。 WIDTH はレジスタのビット幅を指定し、デフォルト値は 4 ビットです。 RESET_VALUE はリセット時に q に設定される値を指定し、デフォルト値は 0 です。 RESET_VALUE の設定値 '0 (アポストロフィゼロ)は、左辺のビット幅に応じたゼロの値を意味します(ビット幅が自動的に設定されます)。

モジュール内の記述では always_ff 文を使用して、クロック clock の立ち上がりエッジで動作する回路を設計しています。 always 文の中では以下のように if 文を使用して条件により動作を変えることができます。

  always_ff @ (posedge clock) begin
    if (reset == 1'b1) begin
      q <= RESET_VALUE; // reset が 1 のときは q にリセット値を設定
    end else begin 
      q <= d;           // reset が 0 のときは q に d の値を設定
    end 

ここの if 文では reset が 1 のときは q にリセット値 RESET_VALUE を代入し、そうでない場合 ( else ) は d の値を q に代入しています。 なお、リセットはclock の立ち上がりエッジで動作する同期リセットであることに注意しましょう。

register_r モジュールの動作例をタイムチャートで示すと図 5.3 のようになります。 ここでは、ビット幅 WIDTH を 4 ビット、リセット値 RESET_VALUE を 4'b0000 とした場合の例を示しています。 リセット信号 reset が 0 の時は、clock の立ち上がりエッジで入力 d の値が出力 q に設定されますが、 reset が 1 の時は、clock の立ち上がりエッジで出力 q にリセット値 0000 が設定されます。 リセット動作も含め、出力 q の値が変化するのは clock の立ち上がりエッジのタイミングだけであることに注意してください。

register_r_timing

<図 5.3 register_r モジュールの動作例>

この register_r モジュールを呼び出して使うには次のように記述します。 モジュール呼び出し時にパラメータ値を設定しないと、デフォルト値が使用されます。 すなわち、ビット幅 WIDTH は 4 ビット、リセット値 RESET_VALUE4'b0000 が使用されます。

shell.sv
module shell(
  input   logic       KEY0,   // clock
  input   logic       SW9,    // reset
  input   logic [3:0] SW,     // d[3:0]
  output  logic [3:0] LEDR    // q[3:0]
);

  register_r reg_r( // ビット幅 4 ビット リセット値は 4'b0000 のレジスタ
    .clock    (KEY0),
    .reset    (SW9),
    .d        (SW),
    .q        (LEDR)
  );

endmodule

演習 1

上記の register_r モジュールを搭載した shell モジュールを実習ボード DE0-CV に実装して、その動作を確かめてみましょう。 shell モジュールをプロジェクトの Top level design entity として設定し、その入出力を表 5.3 に示すようにデバイスへ割り当ててください。

<表 5.3 shell モジュールの入出力のデバイスへの割り当て>

信号名割り当てデバイス入出力
KEY0KEY0input
SW9SW9input
SW[3:0]SW3-SW0input
LEDR[3:0]LEDR3-LEDR0output

演習 2

register_r モジュールのリセット値 RESET_VALUE4'b1010 に変更して、レジスタの出力がリセット時に 4'b1010 になるようにしてみましょう。 次の shell モジュールのように、register_r モジュールの呼び出しの際のパラメータ値を変更してみてください。

shell.sv
module shell(
  input   logic       KEY0,   // clock
  input   logic       SW9,    // reset
  input   logic [3:0] SW,
  output  logic [3:0] LEDR
);

  register_r #(.RESET_VALUE (4'b1010)) reg_r( // リセット値 RESET_VALUE を 4'b1010 に設定
    .clock    (KEY0),
    .reset    (SW9),
    .d        (SW),
    .q        (LEDR)
  );

endmodule

この shell モジュールを実習ボード DE0-CV に実装して、その動作を確かめてみましょう。 先ほどの演習と同様に、shell モジュールをプロジェクトの Top level design entity として設定し、 その入出力を表 5.3 に示すようにデバイスに割り当ててください。


5.4 書き込み許可付きレジスタ

5.3 の register_r モジュールは、リセット機能を持つレジスタでしたが、 ここでは、書き込み許可 (write-enable) 機能を持つレジスタを設計します。 リスト 5.4 の register_r_en モジュールは、書き込み許可付きレジスタの設計例です。 入力として書き込み許可信号 en を追加し、en が 1 のときにのみ入力 d の値を出力 q に設定します。

<リスト 5.4 register_r_en モジュール>

register_r_en.sv
module register_r_en #(
  parameter WIDTH = 4,
  parameter logic [WIDTH-1:0] RESET_VALUE = '0
)(
  input   logic             clock,
  input   logic             reset,
  input   logic             en,      // write-enable
  input   logic [WIDTH-1:0] d,
  output  logic [WIDTH-1:0] q
);

  always_ff @ (posedge clock) begin
    if (reset == 1'b1) begin
      q <= RESET_VALUE;
    end else if (en == 1'b1) begin 
      q <= d;
    end
      
  end
  
endmodule

always_ff 文の中では、if 文を用いて reset が 1 のときはリセット値 RESET_VALUEq に代入し、 reset が 0 のときは、en が 1 の場合に限り d の値を q に代入します。en が 0 のときは、q の値は変化しません。 この register_r_en モジュールの動作例をタイムチャートで示すと図 5.4 のようになります。

register_r_en_timing

<図 5.4 register_r_en モジュールの動作例>

この register_r_en モジュールを呼び出して使うには次のように記述します。

shell.sv
module shell(
  input   logic       KEY0,   // clock
  input   logic       SW9,    // reset
  input   logic       SW8,    // write-enable
  input   logic [3:0] SW,
  output  logic [3:0] LEDR
);

  register_r_en reg_r(
    .clock  (KEY0),
    .reset  (SW9),
    .en     (SW8),
    .d      (SW),
    .q      (LEDR)
  );
 
endmodule

演習

書き込み許可付きレジスタ register_r_en モジュールを搭載した shell モジュールを実習ボード DE0-CV に実装して、その動作を確かめてみましょう。 shell モジュールをプロジェクトの Top level design entity として設定し、その入出力を表 5.4 に示すようにデバイスへ割り当ててください。

<表 5.4 shell モジュールの入出力のデバイスへの割り当て>

信号名割り当てデバイス入出力
KEY0KEY0input
SW9SW9input
SW8SW8input
SW[3:0]SW3-SW0input
LEDR[3:0]LEDR3-LEDR0output

5.5 10進カウンタ

レジスタを用いると、カウンタを設計することもできます。 ここでは図 5.5 に示すような 10 進カウンタを設計します。 このカウンタはクロック信号 clock の立ち上がりのタイミングで、 4ビットの出力信号 count を 0 から 9 までカウントアップし、9 に達すると 0 に戻る動作をします。

counter10

<図 5.5 10進カウンタ>

リスト 5.4 の counter10 モジュールは、10 進カウンタの設計例です。

<リスト 5.4 counter10 モジュール>

counter10.sv
module counter10(
  input   logic       clock,
  input   logic       reset,
  output  logic [3:0] count
);

  logic [3:0] count_next;

  assign count_next = (count == 4'd9) ? 4'd0 : count + 1'd1;

  register_r reg_count(
    .clock  (clock),
    .reset  (reset),
    .d      (count_next),
    .q      (count)
  );

endmodule

この counter10 モジュールは、クロック信号 clock とリセット信号 reset を入力とし、4ビットの出力信号 count を持ちます。 count は現在のカウント値を表し、4 ビットの2進数表現で 0 から 9 までの値をカウントします。 reset 信号は、カウンタをリセットするための信号で、1 のときにカウンタを 0 にリセットします。

モジュール内部では register_r モジュールを使用して、カウンタの値を保持します。 また、モジュール内部で宣言された 4 ビットの信号 count_next は次のカウント値を計算するための信号で、現在のカウント値 count に基づいて次の値を決定します。 コード中の assign 文の部分では、現在のカウント値 count が 9 のときは次のカウント値を 0 にリセットし、それ以外の場合は現在のカウント値に 1 を加えた値を次のカウント値としています。

assign count_next = (count == 4'd9) ? 4'd0 : count + 1'd1;

なお、この部分は図 5.5 の破線で囲んだ部分に対応しています。

register_r モジュールでは次のカウント値 count_next を現在のカウント値 count に更新します。 クロック信号 clock の立ち上がりエッジで count_next の値を count に代入し、出力しています。

リスト 5.4 の counter10 モジュールを呼び出して使うには次のように記述します。

shell.sv
module shell(
  input   logic       KEY0,   // clock
  input   logic       SW9,    // reset
  output  logic [3:0] LEDR
);

  counter10 counter(
    .clock  (KEY0),
    .reset  (SW9),
    .count  (LEDR)
  );
endmodule

演習

上記の counter10 モジュールを搭載した shell モジュールを実習ボード DE0-CV に実装して、その動作を確かめてみましょう。 プロジェクトには shell モジュールと counter10 モジュールに加えて、register_r モジュールも必要となります。 shell モジュールをプロジェクトの Top level design entity として設定し、その入出力を表 5.5 に示すようにデバイスへ割り当ててください。

<表 5.5 shell モジュールの入出力のデバイスへの割り当て>

信号名割り当てデバイス入出力
KEY0KEY0input
SW9SW9input
LEDR[3:0]LEDR3-LEDR0output

5.6 N 進カウンタ

先ほどの 10 進カウンタは、カウント値が 0 から 9 までの範囲で動作していました。 ここでは、カウント値の範囲をパラメータ化して、任意の N 進カウンタを設計します。 リスト 5.5 の counterN モジュールは、N 進カウンタの設計例です。 ビット幅 WIDTH とカウント値の最大値 MAX をパラメータとして指定し、カウント値が 0 から MAX までをカウントアップします。 これにより、N 進カウンタ (N = MAX + 1) を実現します。

<リスト 5.5 counterN モジュール>

counterN.sv
module counterN #(
  parameter WIDTH = 4,
  parameter [WIDTH-1:0] MAX = '1
)(
  input   logic             clock,
  input   logic             reset,
  output  logic [WIDTH-1:0] count
);

  logic [WIDTH-1:0] count_next;

  assign count_next = (count == MAX) ? '0 : count + 1'd1;

  register_r #(.WIDTH (WIDTH)) reg_count(
    .clock  (clock),
    .reset  (reset),
    .d      (count_next),
    .q      (count)
  );

endmodule

counterN モジュールを使って、例えば 172 までカウントアップするカウンタを設計するには、次のように記述します。 なお、カウント値が見やすくなるよう、カウンタの出力を 7セグメントディスプレイに変換するための sseg_decoder モジュールも使用しています。

shell.sv
module shell(
  input   logic       KEY0,   // clock
  input   logic       SW9,    // reset
  output  logic [6:0] HEX1,   
  output  logic [6:0] HEX0    
);
  
  logic [7:0] count;

  counterN #(.WIDTH(8), .MAX(171)) counter172( // ビット幅 8 ビット 最大値 171 のカウンタ
    .clock  (KEY0),
    .reset  (SW9),
    .count  (count)
  );

  // count の上位 4 ビットを7セグメントディスプレイに変換
  sseg_decoder dec1(
    .num  (count[7:4]),
    .hex  (HEX1)
  );

  // count の下位 4 ビットを7セグメントディスプレイに変換
  sseg_decoder dec0(
    .num  (count[3:0]),
    .hex  (HEX0)
  );

endmodule

演習

上記の counterN モジュールを搭載した shell モジュールを実習ボード DE0-CV に実装して、その動作を確かめてみましょう。 プロジェクトには shell モジュールと counterN モジュールに加えて、register_r モジュール と sseg_decoder モジュールも必要となります。 shell モジュールをプロジェクトの Top level design entity として設定し、その入出力を表 5.6 に示すようにデバイスへ割り当ててください。

<表 5.6 shell モジュールの入出力のデバイスへの割り当て>

信号名割り当てデバイス入出力
KEY0KEY0input
SW9SW9input
HEX1[6:0]HEX16-HEX10output
HEX0[6:0]HEX06-HEX00output

5.7 カウント許可付き N 進カウンタ

N 進カウンタ counterN を構成するレジスタ (リセット付きレジスタ register_r ) を書き込み許可付きレジスタ register_r_en に取り替えることで、 カウント許可付きカウンタを構成することができます。 図 5.7 に示すようなカウント許可付き N 進カウンタを設計します。 このカウント許可付き N 進カウンタは、カウント許可信号 en が 1 のときにのみカウントアップを行います。

counterN_en

< 図 5.7 カウント許可付き N カウンタ counterN_en >

リスト 5.7 の counterN_en モジュールは、カウント許可付き N 進カウンタの設計例です。 カウンタのビット幅 WIDTH とカウント値の最大値 MAX をパラメータとして指定します。 カウント許可信号 en 、クロック信号 clock 、リセット信号 reset を入力とし、カウント値 count (ビット幅 WIDTH ) を出力します。

<リスト 5.7 counterN_en モジュール>

counterN_en.sv
module counterN_en #(
  parameter WIDTH = 4,
  parameter logic [WIDTH-1:0] MAX = '1
)(
  input   logic             clock,
  input   logic             reset,
  input   logic             en,     // count-enable
  output  logic [WIDTH-1:0] count
);

  logic [WIDTH-1:0] next_count;
  
  assign next_count = (count == MAX) ? '0 : count + 1'd1;

  register_r_en #(.WIDTH(WIDTH)) reg_count(
    .clock  (clock),
    .reset  (reset),
    .en     (en),
    .d      (next_count),
    .q      (count)
  );

endmodule

この counterN_en モジュールの動作例をタイムチャートで示すと図 5.7a のようになります。 ここでは ビット幅 WIDTH を 4 ビット、カウント値の最大値 MAX を 9 とした 10 進カウンタの例を示しています。 カウント許可信号 en が 1 のときは、クロック信号 clock の立ち上がりエッジでカウントアップが行われますが、 en が 0 のときは、カウント値 count は変化しません。

counterN_en_timing

<図 5.7a カウント許可付き N カウンタ counterN_en モジュールの動作例>

演習

上記の counterN_en モジュールを搭載した shell モジュールを実習ボード DE0-CV に実装して、その動作を確かめてみましょう。

shell.sv
module shell(
  input   logic       KEY0, // clock
  input   logic       SW9,  // reset
  input   logic       SW8,  // count enable
  output  logic [3:0] LEDR
);

  counterN_en #(.MAX(4'd9)) counter10( // ビット幅 4 ビット 最大値 9 のカウンタ
    .clock  (KEY0),
    .reset  (SW9),
    .en     (SW8),
    .count  (LEDR)
  );

endmodule

プロジェクトには shell モジュールと counterN_en モジュールに加えて、register_r_en モジュールも必要となります。 shell モジュールをプロジェクトの Top level design entity として設定し、その入出力を表 5.7 に示すようにデバイスへ割り当ててください。

<表 5.7 shell モジュールの入出力のデバイスへの割り当て>

信号名割り当てデバイス入出力
KEY0KEY0input
SW9SW9input
SW8SW8input
LEDR[3:0]LEDR3-LEDR0output

6章 レジスタを用いた応用回路の設計

ここでは、レジスタを用いた応用回路の設計を行います。 複数のレジスタからなる回路を共通のクロック信号で同期して動作させることでより複雑な回路を設計することができます。 実習ボード DE0-CV では、50MHz のクロック信号が供給されているので、このクロック信号を利用して、周期的な動作をもつ回路を設計することができます。

6.1 クロックの立ち下がりで値をセットするレジスタ

5 章ではクロックの立ち上がりのタイミングで値をセットするレジスタ回路を設計しました。 ここでは、後での応用を考えて、クロックの立ち下がりのタイミングで値をセットするレジスタを設計します。

register_r_en_n

<図 6.1 register_r_en_n モジュール>

以下のリスト 6.1 は、クロックの立ち下がりでレジスタに値をセットする register_r_en_n モジュールです。 5.1 で設計した register_r_en モジュールと同様に、リセットと書き込み許可機能を持っています。 パラメータ WIDTH でレジスタのビット幅を指定でき、パラメータ RESET_VALUE でリセット時の値を指定できます。

< リスト 6.1 書き込み許可付きレジスタ register_r_en_n (クロックの立ち下がりで値をセット) >

register_r_en_n.sv
module register_r_en_n #(
  parameter WIDTH = 4,
  parameter logic [WIDTH-1:0] RESET_VALUE = '0
)(
  input   logic             clock,
  input   logic             reset,
  input   logic             en,
  input   logic [WIDTH-1:0] d,
  output  logic [WIDTH-1:0] q
);
  
  always_ff @ (negedge clock) begin // クロックの立ち下がりで起動
    if (reset == 1'b1) begin 
      q <= RESET_VALUE;
    end else if (en == 1'b1) begin
      q <= d;
    end 
  end

endmodule

この register_r_en_n モジュールは、クロック信号 clock の立ち下がりエッジで起動します。 書き込み許可信号 en が 1 のときに、入力信号 d の値が出力信号 q にセットされラッチされます。 リセット信号 reset が 1 のときは、出力 q にリセット値 RESET_VALUE がセットされます。

図 6.1a に register_r_en_n モジュールの動作例をタイムチャートで示します。 パラメータ WIDTH を 4 とし、リセット値 RESET_VALUE を 4'b0000 とした場合の動作例です。

register_r_en_n モジュールの動作例

<図 6.1a register_r_en_n モジュールの動作例>

演習

リスト 6.1 の register_r_en_n モジュール搭載した以下の shell モジュールを実習ボード DE0-CV に実装してその動作を確認しましょう。

shell.sv
module shell(
  input   logic       KEY0,   // clock
  input   logic       SW9,    // reset
  input   logic       SW8,    // write-enable
  input   logic [3:0] SW,     // d (SW3-SW0)
  output  logic [3:0] LEDR    // q (LEDR3-LEDR0)
);

  register_r_en_n reg4_n( // WIDTH = 4, RESET_VALUE = 4'b0000
    .clock (KEY0),
    .reset (SW9),
    .en    (SW8),
    .d     (SW),
    .q     (LEDR)
  );
  
endmodule

shell モジュールをプロジェクトの Top level design entity として設定し、その入出力を表 6.1 に示すようにデバイスへ割り当ててください。

<表 6.1 shell モジュールの入出力のデバイスへの割り当て>

信号名割り当てデバイス入出力
KEY0KEY0input
SW9SW9input
SW8SW8input
SWSW3-SW0input
LEDRLEDR3-LEDR0output

クロックの立ち上がりでレジスタに値をセットする register_r_en モジュールとの動作の違いを確認しましょう。


6.2 N 進カウンタ (クロックの立ち下がりをカウント)

6.1 で設計した register_r_en_n モジュールを利用して、リセット付きの N 進カウンタを設計します。

counterN_n

<図 6.2 counterN_n モジュール>

次のリスト 6.2 は、クロックの立ち下がりでカウントアップするリセット付き N 進カウンタ counterN_n モジュールです。 パラメータ WIDTH でカウンタのビット幅を指定でき、パラメータ MAX でカウントの最大値を指定できます。 これにより、0 から MAX までをカウントする N 進カウンタ (N = MAX + 1) が実現できます。

<リスト 6.2 N 進カウンタ counterN_n (クロックの立ち下がりでカウントアップ)>

counterN_n.sv
module counterN_n #(
  parameter WIDTH = 4,
  parameter logic [WIDTH-1:0] MAX = '1
)(
  input   logic             clock,
  input   logic             reset,
  output  logic [WIDTH-1:0] count
);
  
  logic [WIDTH-1:0] next_count;
  
  assign  next_count = (count == MAX) ? '0 : count + 1'd1;
  
  register_r_en_n#(.WIDTH(WIDTH)) reg_count(
    .clock    (clock),
    .reset    (reset),
    .en       (1'b1),
    .d        (next_count),
    .q        (count)
  );

endmodule

演習

リスト 6.2 の counterN_n モジュールを搭載した以下の shell モジュールを実習ボード DE0-CV に実装してその動作を確認しましょう。

shell.sv
module shell(
  input   logic       KEY0,   // clock
  input   logic       SW9,    // reset
  output  logic [3:0] LEDR0   // count
);
  
  // 10 進カウンタ
  counterN_n#(.MAX(4'd9)) counter10( // WIDTH = 4, 
    .clock  (KEY0),
    .reset  (SW9),
    .count  (LEDR0)
  );


endmodule

shell モジュールをプロジェクトの Top level design entity として設定し、その入出力を表 6.2 に示すようにデバイスへ割り当ててください。 プロジェクトには以下のモジュールを定義したファイルが必要となります。

  • shell (上記の shell モジュール)
  • counterN_n (リスト 6.2 の N 進カウンタ)
  • register_r_en_n (リスト 6.1 のレジスタ)

<表 6.2 shell モジュールの入出力のデバイスへの割り当て>

信号名割り当てデバイス入出力
KEY0KEY0input
SW9SW9input
LEDR0LEDR3-LEDR0output

6.3. N 分周パルス発生器

N 進カウンタを利用して、クロック信号を N 分周するパルス発生器を設計します。 図 6.3 に N 分周パルス発生器 pulse_dividerN モジュールを示します。 内部の N 進カウンタ counterN_n を利用して (N = MAX + 1)、クロック clock の立ち下がりでカウントアップし、カウント値が MAX に達したときにちょうど 1 クロック分のパルス信号を pulse から出力します。 これにより、クロック信号を MAX + 1 分周したパルス信号を出力します。

pulse_dividerN

<図 6.3 pulse_dividerN モジュール>

次のリスト 6.3 は、N 分周パルス発生器 pulse_dividerN モジュールです。 パラメータ WIDTH でカウンタのビット幅を指定し、パラメータ MAX でカウントの最大値を指定します。

<リスト 6.3 N 分周パルス発生器 pulse_dividerN>

pulse_dividerN.sv
module pulse_dividerN #(
  parameter WIDTH = 4,
  parameter logic [WIDTH-1:0] MAX = '1
)(
  input   logic clock,  // クロック信号 (立ち下がりでカウントアップ)
  input   logic reset,
  output  logic pulse   // パルス信号 
);

  logic [WIDTH-1:0] count;  // カウンタのカウント値

  assign pulse = (count == MAX) ? 1'b1 : 1'b0; // カウントが MAX のときにパルスを出力
  
  counterN_n #(.WIDTH(WIDTH), .MAX(MAX)) counter( // N 進カウンタ (N = MAX + 1)
    .clock  (clock),
    .reset  (reset),
    .count  (count)
  );
  
endmodule

この pulse_dividerN モジュールの動作例を図 6.3a に示します。 この例では、パラメータ WIDTH を 2、MAX を 2 として、3 分周パルス (N = MAX + 1 = 3) を生成しています。 クロックの立ち下がりエッジでカウント値 count がカウントアップし、その値が 2 (MAX) に達したときにパルス信号 pulse が 1 となります。 なお、カウント値 count は、モジュール内部の信号であり外部からは観測できない信号ですので、タイムチャートでは破線で示しています。 出力信号 pulse は、3 クロックサイクルごとに 1 クロック周期分のパルスを出力します。

pulse_dividerN モジュールの動作例

<図 6.3a pulse_dividerN モジュールの動作例>

演習

リスト 6.3 の pulse_dividerN モジュールを搭載した以下の shell モジュールを実習ボード DE0-CV に実装してその動作を確認しましょう。

shell.sv
module shell(
  input   logic     KEY0, // clock
  input   logic     SW9,  // reset
  output  logic     LEDR0 // pulse 
);

  pulse_dividerN #(.WIDTH(4), .MAX(4'd9)) // 10 分周パルス発生器 
  pulse_div10(
    .clock  (KEY0),
    .reset  (SW9),
    .pulse  (LEDR0)
  );

endmodule

shell モジュールをプロジェクトの Top level design entity として設定し、その入出力を表 6.3 に示すようにデバイスへ割り当ててください。

<表 6.3 shell モジュールの入出力のデバイスへの割り当て>

信号名割り当てデバイス入出力
KEY0KEY0input
SW9SW9input
LEDR0LEDR3output

6.4 1 秒ごとにカウントアップするカウンタ

実習ボード DE0-CV には 50MHz のクロックが供給されています。 このクロックを利用して、1 秒ごとにカウントアップする 10 進カウンタを設計します。

6.3 で設計した N 分周パルス発生器を用いて、50MHz クロックを 50,000,000 分周するパルス信号を生成すると、1 秒ごとに 1 クロック分のパルス信号が出力されます。 このパルス信号をカウント許可付きカウンタのカウント許可信号として利用し、1 秒ごとにカウントアップする 10 進カウンタを実現します。

図 6.4 に回路の構成を構成を示します。 5000 万分周パルス発生器 pulse_dividerN_n (WIDTH = 26, MAX = 49_999_999) と、 10 進カウンタ counterN_en (WIDTH = 4, MAX = 9) を組み合わせて、1 秒ごとにカウントアップする 10 進カウンタを実現しています。

counter10_onesec

<図 6.4 1 秒ごとにカウントアップする 10 進カウンタ>

図 6.4a 回路の動作例をタイムチャートで示します。 5000 万分周パルス発生器 pulse_dividerN_n は、50MHz クロックの立ち下りを 50,000,000 回カウントするたびに、クロック 1 周期分のパルス信号 onesec_pulse を出力します。 すなわち、1 秒ごとに 1 クロック分のパルス信号を出力します。 10 進カウンタ counterN_en は、この onesec_pulse 信号をカウント許可信号として利用して、50 MHz クロックの上がりカウントアップします。 なお、カウント許可信号として用いる onesec_pulse 信号が 1 となっている状態で安定的にクロック信号の立ち上がりが来るように、パルス発生器 pulse_dividerN_n はクロックの立ち下がりで起動し、10 進カウンタ counterN_en はクロックの立ち上がりで起動するようにしています。

counter10_onesec_timing

<図 6.4a 回路の動作例>

次のリスト 6.4 は、1 秒ごとにカウントアップする 10 進カウンタを実現する shell モジュールです。

shell.sv
module shell(
  input   logic       CLOCK_50, // clock (50MHz)
  input   logic       SW9,      // reset
  output  logic [3:0] LEDR     // count
);

  logic onesec_pulse;  // 1 秒ごとのパルス信号
  
  // 1 秒ごとのパルス発生器 (50MHz クロックを 50,000,000 分周)
  pulse_dividerN #(.WIDTH(26), .MAX(26'd49_999_999)) onesec_gen(
    .clock  (CLOCK_50),
    .reset  (SW9),
    .pulse  (onesec_pulse)
  );
  
  // 10 進カウンタ (1 秒ごとにカウントアップ)
  counterN_en #(.WIDTH(4), .MAX(4'd9)) counter10(
    .clock  (CLOCK_50),
    .reset  (SW9),
    .en     (onesec_pulse),
    .count  (LEDR)
  );
  
endmodule

演習

リスト 6.4 の shell モジュールを実習ボード DE0-CV に実装してその動作を確認しましょう。 DE0-CV のでは、CLOCK_50 信号が 50MHz のクロック信号として供給されています。 shell モジュールをプロジェクトの Top level design entity として設定し、その入出力を表 6.3 に示すようにデバイスへ割り当ててください。

<表 6.4 shell モジュールの入出力のデバイスへの割り当て>

信号名割り当てデバイス入出力
CLOCK_50CLOCK_50input
SW9SW9input
LEDRLEDR3-LEDR0output

6.5 課題 (100 Hz のパルス信号発生器)

実習ボード DE0-CV では、50MHz のクロック信号が供給されています。 このクロック信号を利用して、100 Hz のパルス信号を発生させるパルス発生器を設計しましょう。

発生させるパルス信号は図 6.5 に示すような 100 Hz の矩形波です。 1 周期 \(T\) のうち、パルスが 1 となる時間を \(T_{on} = 0.3 T\) と設定してください。

pulse_100Hz

<図 6.5 100 Hz のパルス波形>

発生したパルス信号は、実習ボードの GPIO Header のいずれかのピン (例えば GPIO_1_D1) に出力し、オシロスコープを用いて出力波形を確認してください。

7章 ステートマシンの設計

前章までで、レジスタの設計方法と組み合わせ回路の設計方法、さらにそれらの回路モジュールを組み合わせて新しい回路を設計する方法を学んできました。 レジスタと組み合わせ回路とを組み合わせることで、ステートマシン(有限状態機械)を設計することができます。 ステートマシンは内部に状態を持ち、入力に応じて状態を遷移し、出力を生成する回路です。 ステートマシンは、コンピュータのCPUや、通信プロトコル、制御回路など、様々な分野で広く利用されています。 ステートマシンの設計方法を学ぶことで、より複雑な回路を設計できるようになります。

7.1 Moore 型ステートマシンの設計

Moore 型ステートマシンは、出力が現在の状態のみに依存するステートマシンです。 状態遷移は入力と現在の状態に基づいて次の状態が決まりますが、出力は現在の状態にのみ依存します。

ここではステートマシンの例として、図 7.1a に示すような 1 bit の入力 p、クロック入力 clock、同期リセット信号 reset、1 bit の出力 y を持つステートマシン my_stm1を設計します。

ステートマシン my_stm1

<図7.1a ステートマシン my_stm1 とその状態遷移図>

ステートマシン my_stm1 の状態遷移は図 7.1a の右側に示す状態遷移図で与えます。 my_stm1 は 3 つの状態(SA, SB, SC)を持ち、出力 y が状態だけで決まるような Moore 型のステートマシンです。 状態遷移は clock の立ち上がりのタイミングで起こるものとします。 同期リセット信号 reset が 1 になると、clock の立ち上がりで状態は SA にリセットされます。

my_stm1 の期待される動作例を図 7.1b にタイムチャートで示します。 (状態は外部からは見えないので、破線で示しています。)

my_stm1 の動作例

<図7.1b my_stm1 の動作例>

クロックの立ち上がりで状態が遷移し、その状態に応じて出力 y が変化することがわかります。

以下ではこのステートマシン my_stm1 を設計していきます。

状態の符号化

ステートマシン my_stm1 は 3 つの状態を持ちます。 ステートマシンを実現するには、現在どの状態にあるかを表現し保持するために、取りうる状態を 2 進数のビット列で符号化する必要があります。 状態の符号化にはいろいろな方法がありますが、ここでは one-hot 符号化と呼ばれる方法を用いることにします。

one-hot 符号化では、状態の数だけビットを用意し、現在の状態に対応するビットだけが 1 で他は 0 となるように符号化します。 この場合、3 つの状態(SA, SB, SC)を表すには 3 ビットのレジスタが必要です。 以下の表 7.1a に、状態と符号化されたビット列を示します。

<表7.1a 状態の符号化>

状態符号
SA100
SB010
SC001

状態レジスタの設計

状態マシン my_stm1 の現在の状態を保持するために、3 ビットのレジスタを用意します。 ここでは、このレジスタを状態レジスタと呼ぶことにします。

状態レジスタは 5.3 節で学んだ同期リセット付きレジスタを用いて設計できます。 同期リセット付きレジスタ register_r モジュールの HDL 記述を再掲します。

register_r.sv

<リスト7.1a register_r モジュール(同期リセット付きレジスタ)>

register_r.sv
module register_r #(
  parameter WIDTH = 4,  // 入出力のビット幅 (デフォルトは 4)
  parameter logic [WIDTH-1:0] RESET_VALUE = '0 // リセット時の値 (デフォルトは 0)  
)(
  input   logic             clock,
  input   logic             reset, // reset
  input   logic [WIDTH-1:0] d,
  output  logic [WIDTH-1:0] q
);

  always_ff @ (posedge clock) begin
    if (reset == 1'b1) begin
      q <= RESET_VALUE; // reset が 1 のときは q にリセット値を設定
    end else begin 
      q <= d;           // reset が 0 のときは q に d の値を設定
    end 
  end

endmodule

ステートマシン my_stm1 の状態は 3 ビットで表現され、リセット時には状態 SA にリセットされます。 したがって次のように register_r モジュールについて、そのパラメータを WIDTH = 3, RESET_VALUE = 3'b100 と設定してインスタンス化することで、状態レジスタを作成できます。

  register_r #(
    .WIDTH(3),            // 3 ビットのレジスタ 
    .RESET_VALUE(3'b100)  // SA にリセット
  ) state_register (
    .clock(clock),
    .reset(reset),
    .d(next_state),
    .q(state)
  );

次状態生成回路の設計

次状態生成回路は、現在の状態と入力に基づいて次の状態を決定する組み合わせ回路です。

状態生成回路を設計するために、まず、図 7.1a の状態遷移図をもとに、現在の状態と入力 p の組み合わせから次の状態を決定する状態遷移表を作成します。

<表7.1b my_stm1 の状態遷移表>

現在の状態入力 p次の状態
SA0SA
SA1SB
SB0SC
SB1SA
SC0SC
SC1SA

現在の状態と次の状態を表 7.1a によって符号化することで次状態生成回路の真理値表を得ることができます。 以降では、次状態生成回路の入力となる現在の状態を state, 出力となる次の状態を next_state で表すこととします。 表 7.1c に、符号化後の次状態生成回路の真理値表を示します。 なお、状態遷移表に現れない入力値の組み合わせ(例えば state = 011, p = 0)に対しては、デフォルトで SA にリセットされるようにしています。

<表7.1c my_stm1 の次状態生成回路の真理値表>

statepnext_state
1000100
1001010
0100001
0101100
0010001
0011100
その他*100

リスト 7.1b に、次状態生成回路 next_state_generator モジュールの HDL 記述を示します。 always_comb 文の中で case 文を用いて、表 7.1c の入出力の関係を組合せ回路として記述しています。 なお、表に現れない入力値の組み合わせ(例えば state = 011, p = 0)に対しては、デフォルトで SA にリセットされるようにしています。

<リスト 7.1b next_state_generator モジュール(次状態関数回路)>

next_state_generator.sv
module next_state_generator (
  input   logic [2:0] state,      // 現在の状態
  input   logic       p,          // 入力
  output  logic [2:0] next_state  // 次の状態
);

  always_comb begin
    case ({state, p}) // state と p を連結して状態遷移を定義
      4'b100_0: next_state = 3'b100; // SA -- p=0 --> SA
      4'b100_1: next_state = 3'b010; // SA -- p=1 --> SB
      4'b010_0: next_state = 3'b001; // SB -- p=0 --> SC
      4'b010_1: next_state = 3'b100; // SB -- p=1 --> SA
      4'b001_0: next_state = 3'b001; // SC -- p=0 --> SC
      4'b001_1: next_state = 3'b100; // SC -- p=1 --> SA
      default:  next_state = 3'b100; // デフォルトは SA にリセット
    endcase
  end

endmodule

出力生成回路の設計

出力生成回路は、現在の状態に基づいて出力を生成する組み合わせ回路です。

図7.1aの状態遷移図から、各状態における出力 y の値を表 7.1d に示します。

<表7.1d 状態と出力の対応表>

今の状態出力 y
SA0
SB1
SC0

先ほどの次状態生成回路と同様に、状態を符号に置き換えることで、表 7.1e に示すような出力生成回路 output_decoder の真理値表が得られます。 状態と出力の対応表に現れない入力値の組み合わせ(例えば state = 011)に対しては、デフォルトで出力 0 としています。

<表7.1e 出力生成回路の真理値表>

statey
1000
0101
0010
その他0

リスト 7.1c に、出力生成回路 output_decoder モジュールの HDL 記述を示します。 always_comb 文の中で case 文を用いて、表 7.1e の入出力の関係を組合せ回路として記述しています。 なお、表に現れない入力値の組み合わせ(例えば state = 011)に対しては、デフォルトで出力 0 としています。

<リスト 7.1c output_decoder モジュール(出力生成回路)>

output_decoder.sv
module output_decoder (
  input   logic [2:0] state, // 今の状態
  output  logic       y      // 出力
);

  always_comb begin
    case (state)
      3'b100: y = 1'b0; // SA のときは出力 0
      3'b010: y = 1'b1; // SB のときは出力 1
      3'b001: y = 1'b0; // SC のときは出力 0
      default: y = 1'b0; // デフォルトは出力 0
    endcase
  end

endmodule

ステートマシンの組み上げ

以上で、状態レジスタ register_r モジュール、次状態生成回路 next_state_generator モジュール、出力生成回路 output_decoder を設計することができました。

これらの各モジュールを図 7.1c のように接続することで、ステートマシン my_stm1 を構築することができます。

ステートマシン my_stm1 の構成図

<図7.1c my_stm1 の構成図>

この構成図をもとに作成した my_stm1 モジュールの HDL 記述をリスト 7.1d に示します。

<リスト 7.1d my_stm モジュール>

my_stm1.sv
module my_stm1 (
  input   logic       clock,
  input   logic       reset,
  input   logic       p,
  output  logic       y
);

  logic [2:0] state;      // 現在の状態
  logic [2:0] next_state; // 次の状態

  // 状態レジスタ
  register_r #(
    .WIDTH(3),            // 3 ビットのレジスタ
    .RESET_VALUE(3'b100)  // SA にリセット
  ) state_register (
    .clock(clock),
    .reset(reset),
    .d(next_state),   // 次の状態を入力
    .q(state)         // 現在の状態を出力
  );

  // 次状態関数
  next_state_generator next_state_gen(
    .p          (p),
    .state      (state),        // 現在の状態を入力
    .next_state (next_state)    // 次の状態を出力
  );

  // 出力関数
  output_decoder output_dec(
    .state      (state),    // 現在の状態を入力
    .y          (y)         // 出力を出力 
  );

endmodule //

演習

my_stm1 モジュールを搭載した以下の shell モジュールを実習ボード DE0-CV に実装してその動作を確認しましょう。 以下の shell モジュールを実習ボード DE0-CV に実装してその動作を確認しましょう。

shell.sv
module shell(
  input   logic       KEY0,   // clock
  input   logic       SW9,   // reset
  input   logic       SW0,    // p
  output  logic       LEDR0   // y
);

  my_stm1 stm(
    .clock(KEY0),   // clock
    .reset(SW9),    // reset
    .p(SW0),        // p
    .y(LEDR0)       // y
  );

endmodule

shell モジュールを top level design entity としてプロジェクトに設定し、入出力信号を表 7.1f のように割り当ててください。

<表 7.1f shell モジュールの入出力のデバイスへの割り当て>

信号名割り当てデバイス入出力
KEY0KEY0input
SW9SW9input
SW0SW0input
LEDR0LEDR0output

プロジェクトには shell モジュールに加えて、以下のモジュールを定義したファイルが必要となることに注意してください。

  • my_stm1 (リスト 7.1d のステートマシン)
  • register_r (リスト 7.1a の同期リセット付きレジスタ)
  • next_state_generator (リスト 7.1b の次状態生成回路)
  • output_decoder (リスト 7.1c の出力生成回路)

7.2 列挙型(enum)を用いたステートマシンの設計

ここでは、列挙型(enum)を用いてステートマシンの状態を定義し、より可読性の高いコードでステートマシンを設計する方法を学びます。 列挙型(enum)を用いることで、状態を数値ではなく意味のある名前で表現でき、コードの可読性が向上します。 列挙型を用いたステートマシンの設計方法を学ぶために、前節で設計した my_stm1 モジュールを列挙型を用いて書き直してみます。

列挙型は SystemVerilog で定義できるデータ型の一つで、特定の値の集合を名前付きで定義することができます。

列挙型の定義は次のように行います。

  typedef enum logic [1:0] {
    SA = 2'b00, // 状態 SA
    SB = 2'b01, // 状態 SB
    SC = 2'b10  // 状態 SC
  } state_t;

ここでは、列挙型 state_t を定義し、状態 SA, SB, SC をそれぞれ 2 ビットの値で表現しています。 なお、7.1 節では、状態を 3 ビットで符号化していましたが、ここでは 2 ビット幅の列挙型を用いて符号化しています。 (もちろん、3 ビットで符号化しても問題ありません。)

以下のリスト 7.2 に、列挙型を用いて my_stm1 モジュールを再設計した my_stm2 モジュールの HDL 記述を示します。

<リスト 7.2 列挙型を用いた my_stm2 モジュール>

my_stm2.sv
module my_stm2 (
  input   logic       clock,
  input   logic       reset,
  input   logic       p,
  output  logic       y
);

  // 列挙型で状態を定義
  typedef enum logic [1:0] {
    SA = 2'b00, // 状態 SA
    SB = 2'b01, // 状態 SB
    SC = 2'b10  // 状態 SC
  } state_t;

  state_t state;      // 現在の状態
  state_t next_state; // 次の状態

  // 状態レジスタ
  register_r #(
    .WIDTH(2),           // 列挙型のビット幅は 2
    .RESET_VALUE(SA)     // SA にリセット
  ) state_register (
    .clock(clock),
    .reset(reset),
    .d(next_state),   // 次の状態を入力
    .q(state)         // 現在の状態を出力
  );

  // 次状態生成回路部分
  always_comb begin
    case ({state, p}) // state と p を連結して状態遷移を定義
      {SA, 1'b0}: next_state = SA; // SA -- p=0 --> SA
      {SA, 1'b1}: next_state = SB; // SA -- p=1 --> SB
      {SB, 1'b0}: next_state = SC; // SB -- p=0 --> SC
      {SB, 1'b1}: next_state = SA; // SB -- p=1 --> SA
      {SC, 1'b0}: next_state = SC; // SC -- p=0 --> SC
      {SC, 1'b1}: next_state = SA; // SC -- p=1 --> SA
      default:    next_state = SA; // デフォルトは SA にリセット
    endcase
  end

  // 出力生成回路部分
  always_comb begin
    case (state)
      SA: y = 1'b0; // SA のときは出力 0
      SB: y = 1'b1; // SB のときは出力 1
      SC: y = 1'b0; // SC のときは出力 0
      default: y = 1'b0; // デフォルトは出力 0
    endcase
  end
endmodule

この my_stm2 モジュールは、列挙型を用いて状態を定義し、状態遷移と出力生成を行っています。 現在の状態と次の状態を表す内部信号 statenext_state は、列挙型 state_t を用いて定義されています。 また、case 文を用いて、状態遷移と出力信号の生成を行っていますが、このパターンマッチの部分にも列挙型 state_t の値 (SA, SB, SC) を用いることで、より可読性の高いコードになっています。

演習

my_stm2 モジュールを搭載した以下の shell モジュールを実習ボード DE0-CV に実装してその動作を確認しましょう。

shell.sv
module shell(
  input   logic       KEY0,   // clock
  input   logic       SW9,    // reset
  input   logic       SW0,    // p
  output  logic       LEDR0   // y
);

  my_stm2 stm(
    .clock(KEY0),   // clock
    .reset(SW9),    // reset
    .p(SW0),        // p
    .y(LEDR0)       // y
  );
endmodule

shell モジュールを top level design entity としてプロジェクトに設定し、入出力信号を表 7.2 のように割り当ててください。

<表 7.2 shell モジュールの入出力のデバイスへの割り当て>

信号名割り当てデバイス入出力
KEY0KEY0input
SW9SW9input
SW0SW0input
LEDR0LEDR0output

プロジェクトには shell モジュールに加えて、以下のモジュールを定義したファイルが必要となることに注意してください。

  • my_stm2 (リスト 7.2 のステートマシン)
  • register_r (リスト 7.1a の同期リセット付きレジスタ)

7.3 Mealy 型ステートマシンの設計

Mealy 型ステートマシンは、出力が現在の状態と入力に依存するステートマシンです。 ここでは、図 7.3a に示すような Mealy 型ステートマシン my_stm3 を設計します。

ステートマシン my_stm3

<図 7.3a Mealy 型ステートマシン my_stm3>

my_stm3 は 2 つの状態(SA, SB)を持ち、入力 p と現在の状態に応じて出力 y が決まる Mealy 型のステートマシンです。 状態遷移は clock の立ち上がりのタイミングで起こるものとします。 同期リセット信号 reset が 1 になると、clock の立ち上がりで状態は SA にリセットされます。

my_stm3 の期待される動作例を図 7.3b にタイムチャートで示します。 (状態は外部からは見えないので、破線で示しています。) 出力 y は、状態が SB で、かつ、入力 p が 1 のときに 1 となることに注意してください。

my_stm3 の動作例

状態遷移図をもとに、my_stm3 の状態遷移表を作成すると次のようになります。 Moore 型ステートマシンと同様に、次の状態は現在の状態と入力 p によって決まります。

<表 7.3a my_stm3 の状態遷移表>

現在の状態入力 p次の状態
SA0SA
SA1SB
SB0SB
SB1SB

Mealy 型ステートマシンでは、出力 y は現在の状態と入力 p によって決まります。 対応表を作成すると次のようになります。

<表 7.3b my_stm3 の入力、状態と出力との対応表>

現在の状態入力 p出力 y
SA00
SA10
SB00
SB11

my_stm3 は 2 つの状態(SA, SB)を持ちますが、これらは列挙型を用いて符号化することができます。 列挙型を用いて状態を定義すると次のようになります。 (状態が2つしかないので、1 ビットの列挙型で定義することができます。)

  typedef enum logic {
    SA = 1'b0, // 状態 SA
    SB = 1'b1  // 状態 SB
  } state_t;

この列挙型を用いて、my_stm3 モジュールを設計します。 表 7.3a の状態遷移表と表 7.3b の出力の対応表をもとに作成した、my_stm3 モジュールの HDL 記述をリスト 7.3 に示します。

<図 7.3b my_stm3 の動作例>

my_stm3.sv
module my_stm3 (
  input   logic       clock,
  input   logic       reset,
  input   logic       p,
  output  logic       y
);

  // 列挙型で状態を定義
  typedef enum logic {
    SA = 1'b0, // 状態 SA
    SB = 1'b1  // 状態 SB
  } state_t;

  state_t state;      // 現在の状態
  state_t next_state; // 次の状態

  // 状態レジスタ
  register_r #(
    .WIDTH(1),           // 列挙型のビット幅は 1
    .RESET_VALUE(SA)     // SA にリセット
  ) state_register (
    .clock(clock),
    .reset(reset),
    .d(next_state),   // 次の状態を入力
    .q(state)         // 現在の状態を出力
  );

  // 次状態生成回路
  always_comb begin
    case ({state, p}) // state と p を連結して状態遷移を定義
      {SA, 1'b0}: next_state = SA; // SA -- p=0 --> SA
      {SA, 1'b1}: next_state = SB; // SA -- p=1 --> SB
      {SB, 1'b0}: next_state = SB; // SB -- p=0 --> SB
      {SB, 1'b1}: next_state = SA; // SB -- p=1 --> SA
      default:    next_state = SA; // デフォルトは SA にリセット
    endcase
  end

  // 出力生成回路 (Mealy 型)
  always_comb begin
    case ({state, p}) // state と p を連結して出力を定義
      {SA, 1'b0}: y = 1'b0; // SA, p=0 のときは y=0
      {SA, 1'b1}: y = 1'b0; // SA, p=1 のときは y=0
      {SB, 1'b0}: y = 1'b0; // SB, p=0 のときは y=0
      {SB, 1'b1}: y = 1'b1; // SB, p=1 のときは y=1
      default:    y = 1'b0; // デフォルトは y = 0
    endcase
  end
endmodule

演習

my_stm3 モジュールを搭載した以下の shell モジュールを実習ボード DE0-CV に実装してその動作を確認しましょう。

shell.sv
module shell(
  input   logic       KEY0,   // clock
  input   logic       SW9,    // reset
  input   logic       SW0,    // p
  output  logic       LEDR0   // y
);

  my_stm3 stm(
    .clock(KEY0),   // clock
    .reset(SW9),    // reset
    .p(SW0),        // p
    .y(LEDR0)       // y
  );
endmodule

shell モジュールを top level design entity としてプロジェクトに設定し、入出力信号を表 6.3c のように割り当ててください。

<表 6.3c shellモジュールの入出力のデバイスへの割り当て>

信号名割り当てデバイス入出力
KEY0KEY0input
SW9SW9input
SW0SW0input
LEDR0LEDR0output

プロジェクトには shell モジュールに加えて、以下のモジュールを定義したファイルが必要となることに注意してください。

  • my_stm3 (リスト 6.3 のステートマシン)
  • register_r (リスト 7.1a の同期リセット付きレジスタ)

7.4 課題

図 7.4 に示すような 2ビットの入力 p と 7 ビットの出力 HEX を持つ Moore 型のステートマシン fib_stm を設計しましょう。 状態遷移は clock の立ち上がりのタイミングで起こるものとします。 また、出力 HEX は 7 セグメントディスプレイに接続することを想定しています。 状態に応じて 7 セグメントディスプレイに 1, 2, 3 のパターンを表示させてください。

ステートマシン fib_stm

<図 7.4 ステートマシン fib_stm>

7章 その他

10進カウンタ

count10.sv
module count10 (
  input   logic       clock,
  output  logic [3:0] count
);

  always_ff @ (posedge clock) begin
    if (count >= 4'd9) begin
      count <= 4'd0;
    end else begin
      count <= count + 1'd1;
    end
  end

endmodule

parameter

register.sv
module register #(parameter WIDTH = 8) (
  input                     clock,
  input   logic [WIDTH-1:0] d,
  output  logic [WIDTH-1:0] q
);

  always_ff @ (posedge clock) begin
    q <= d;
  end
endmodule
top.sv
module top (
  input   logic       clock,
  input   logic [1:0] sw_2bit,
  input   logic [7:0] sw_8bit,
  output  logic [1:0] led_2bit,
  output  logic [7:0] led_8bit
);

  // 2-bit レジスタ
  register #(.WIDTH(2)) register_2bit( // registerモジュールのパラメータを上書き(WIDTH = 2)
    .clock  (clock),
    .d      (sw_2bit),
    .q      (led_2bit)
  );

  // 8-bit レジスタ
  register register_8bit (  // パラメータを指定しないとregisterモジュールで指定された値が使われる(WIDTH = 8)
    .clock  (clock),
    .d      (sw_8bit),
    .q      (led_8bit)
  );

endmodule // Top

enum

my_stm.sv
module my_stm (
  input   logic       clock,
  input   logic       n_reset,
  input   logic       p,
  output  logic [1:0] y
);

  // State
  typedef enum logic[1:0] {SA, SB, SC, SD} State;

  State state;

  // next_state_generator + register(with async reset)
  always_ff @ (posedge clock, negedge n_reset) begin
    if (n_reset == 1'b0) begin  // active low async reset
      state <= SA;
    end else begin
      case ({p, state}) //  state transition
        {1'b0, SA}: state <= SB;
        {1'b0, SB}: state <= SC;
        {1'b0, SC}: state <= SD;
        {1'b0, SD}: state <= SA;
        {1'b1, SA}: state <= SA;
        {1'b1, SB}: state <= SB;
        {1'b1, SC}: state <= SC;
        {1'b1, SD}: state <= SD;
        default:    state <= SA;
      endcase
    end
  end

  // output_decoder
  always_comb begin
    case (state)
      SA:       y = 2'b00;
      SB:       y = 2'b01;
      SC:       y = 2'b00;
      SD:       y = 2'b10;
      default:  y = 2'b00;
    endcase
  end

endmodule

ノンブロッキング代入とブロッキング代入のちがい

ノンブロッキング代入

nonblocking.sv
module nonblocking( // shift register
  input   logic clk,
  input   logic d,
  output  logic q0,
  output  logic q1
);

  always_ff @ (posedge clk) begin
    q0 <= d;   // (1)
    q1 <= q0;  // (2)
  end
  
endmodule

ノンブロッキング代入を用いた場合、(1)と(2)の代入が並列で実行されます。 したがって、クロック clk の立ち上がりのタイミングで、 q0 には d の値、q1 には直前の q0 の値が代入されます。

timechart

shift register

ブロッキング代入

blocking.sv
module blocking( // shift register
  input   logic clk,
  input   logic d,
  output  logic q0,
  output  logic q1
);

  always_ff @ (posedge clk) begin
    q0 = d;   // (1)
    q1 = q0;  // (2)
  end
  
endmodule

ブロッキング代入では、上から順に(1)を評価し次に(2)を評価され、その結果、q1 = q0 = d となります。 したがって、クロック clk の立ち上がりのタイミングで、q0, q1 ともに d の値が代入されます。

timechart

register

レポートについて

この実習では課題として、小レポート課題と通常のレポート課題を設定しています。 それぞれに記載する内容をいかに示します。よく確認してレポートを作成してください。

なお、レポートは PDF ファイルとし、ファイル名の拡張子は .pdf としてください。(例: report.pdf)

小レポート課題

小レポート課題では、以下の内容をまとめてください。

外部設計

外部設計では、利用者から見たときの回路の振る舞いを示してください。 どのような入力と出力があるのか、入力と出力の関係はどうなっているのかを説明してください。 順序回路の場合は、どのような状態があるのか、状態遷移はどうなっているのかを説明してください。

また、入力信号や出力信号とそれらを与えるデバイスとの対応関係を明示してください。 例えば、スイッチや LED などのデバイスを使う場合は、どのスイッチがどの入力信号に対応しているのか、 どの LED がどの出力信号に対応しているのかを示しましょう。

内部設計

内部設計では、回路の内部構造を示してください。

まず回路の構成要素を示し、それらの構成要素がどのように接続されているのかを説明してください。 回路モジュール同士がどのように接続されているのか、ブロック図などを用いるとよいでしょう。 次に各構成要素の設計について説明してください。

HDLによる設計の場合は、HDL コードも示してください。 また、HDL コードの各モジュールがどのような役割を果たしているのかを説明してください。 HDL コードの中で、特に重要な部分や注意すべき部分については、コメントを付けて説明してください。

動作結果

動作結果では、実習ボードに実装した回路において、 実際に回路を動かして得られた結果を示してください。 回路が適切に動作していることを示すために必要十分なだけの動作結果を示すとよいでしょう。

動作結果は適宜、図や表などを用いて分かりやすく整理して示してください。

検証

動作結果を分析して、回路が正しく動作していることを根拠とともに説明してください。


レポート課題(自由課題)

最後のレポート課題では、皆さん自身で回路の仕様を考え、設計・実装を行います。 レポート課題では、以下の内容を章立ててまとめてください。

外部設計

みなさん自身が回路の外部仕様を定めて、利用者から見たときの回路の振る舞いを示してください。

回路がどのような機能を持つのか、どのような入力と出力があるのか、 入力と出力の関係はどうなっているのかを説明してください。 順序回路の場合は、どのような状態があるのか、状態遷移はどうなっているのかを説明してください。 また、入力信号や出力信号とそれらを与えるデバイスとの対応関係を明示してください。

内部設計

回路の設計方針を示したうえで、回路の内部構造を示してください。

まず回路の構成要素を示し、それらの構成要素がどのように接続されているのかを説明してください。 回路モジュール同士がどのように接続されているのか、ブロック図などを用いるとよいでしょう。

次に各構成要素の設計について説明してください。 HDLによる設計の場合は、HDL コードも示してください。 HDL コードの各モジュールがどのような役割を果たしているのかを説明してください。 また、HDL コードの中で、特に重要な部分や注意すべき部分については、コメントを付けて説明してください。

動作結果

動作結果では、皆さんが作成した回路が適切に動作しているかを確認するために、実際に回路を動かして得られた結果を示してください。 いわゆるテストケースとして、動作確認の手順を示し、期待される結果を示したうえで、実際に回路を動作されて得られた結果を示してください。

回路が適切に動作していることを示すために必要十分なだけの動作結果を示すとよいでしょう。 動作結果は適宜、図や表などを用いて分かりやすく整理して示してください。

検証

動作結果を分析して、回路が外部仕様に基づいて適切に動作しているのか、動作結果を引用しながらその根拠とともに説明してください。

考察

回路の設計から実装および検証について、どのような点がうまくいったのか、どのような点が難しかったのか、また、どのような点を改善すればよりよい回路になるのかなどについて考察してください。

参考文献

参考にした文献や資料があれば、リストにして示してください。 参考文献を示す際は、著者名、タイトル、出版社、発行年、URL などを明示してください。 また、参考文献は、レポートの中で適宜引用してください。 引用の際は、どの参考文献を参照したのかを明示してください。 例えば、[1]のように番号を付けて引用し、参考文献の一覧でその番号に対応する文献を示すとよいでしょう。

所感

今回の実習を通して学んだことや感じたこと、今後の学習や実習に活かしたいことなどを自由に記述してください。なお、所感は評価の対象にはなりません。