// Data Memory module dmem(clk, wr, addr, din, dout); input clk, wr; input [3:0] addr, din; output [3:0] dout; reg [3:0] mem[0:15]; always @(posedge clk) begin // operation // .... end assign dout = .....; endmoduleここでポイントは、5行目のメモリの実体である変数memの宣言です。 「reg [3:0]」とすることで、4ビット幅の変数(reg型)が宣言できますが、 これを「mem[0:15]」とすることで、15個分の配列として宣言しています。 すなわちmemは4ビット×15個の配列、ということになります。 ちなみにmem[0]は0番地のデータ4ビット、mem[0][3]は その0番地のデータの最上位ビット、となります。 これは次のような二次元配列と考えるとわかりやすいでしょう。
0番地 | mem[0][3] | mem[0][2] | mem[0][1] | mem[0][0] | ←4ビットまとめてmem[0] |
1番地 | mem[1][3] | mem[1][2] | mem[1][1] | mem[1][0] | ←4ビットまとめてmem[1] |
... | ... | ... | ... | ... | ... |
15番地 | mem[15][3] | mem[15][2] | mem[15][1] | mem[15][0] | ←4ビットまとめてmem[15] |
命令(op) | ニーモニック表記 | 動作内容 |
0000 | mov imm, r0 | immをr0に代入 |
0001 | mov imm, r1 | immをr1に代入 |
0010 | add r0, imm, r0 | r0+immをr0に代入 |
0011 | add r0, imm, r1 | r0+immをr1に代入 |
0100 | add r1, imm, r0 | r1+immをr0に代入 |
0101 | add r1, imm, r1 | r1+immをr1に代入 |
0110 | jmp imm | imm番地へジャンプ(無条件分岐) |
0111 | jz imm | Zフラグ=1ならばimm番地へ分岐、それ以外は次の命令へ |
1000 | mov r0, @imm | r0の内容をデータメモリのimm番地へ書き込み |
1001 | mov r1, @imm | r1の内容をデータメモリのimm番地へ書き込み |
1010 | mov @imm, r0 | データメモリのimm番地の内容をr0へ代入 |
1011 | mov @imm, r1 | データメモリのimm番地の内容をr1へ代入 |
module sample(CLK6, LED, SW, SG, SA); input CLK6; input [3:0] SW; output [7:0] LED, SG; output [3:0] SA; reg [3:0] r0, r1, pc, daddr, d2io; wire [3:0] iaddr, d2cpu, op, imm; wire [7:0] idata; wire [3:0] d3, d2, d1, d0; reg mem_wr, st, z; wire clk, rst; imem i0(iaddr, idata); dmem i1(clk, mem_wr, daddr, d2io, d2cpu); sw_clk iclk(CLK6, ~SW[0], clk); seg7 iseg7(CLK6, d3, d2, d1, d0, SG, SA); assign LED = {z, st, clk, 1'b0, op}; assign d0 = r0, d1 = r1, d2 = iaddr, d3 = daddr; assign rst = ~SW[3]; assign iaddr = pc; assign op = idata[7:4], imm = idata[3:0]; always @(posedge clk or posedge rst) begin if (rst == 1'b1) begin pc <= 4'h0; st <= 1'b0; z <= 1'b0; mem_wr <= 1'b0; daddr <= 4'h0; r0 <= 4'h0; r1 <= 4'h0; end else begin if (st == 0) begin st <= 1'b1; mem_wr <= 1'b0; case (op) // operation // resister access, mem_wr setf for op=1000/1001 .... endcase end else begin st <= 1'b0; // memory read for op=1010/1011 case (op) ... endcase // update pc if (op == 4'b0110) ... end end end endmodule
演習3-2 sample_cpu2.zipをダウンロードして展開し、 この中のsample.vを参考に、これらの命令を拡張したCPUを 設計してみましょう。 データメモリdmemは、前回と同様に新しいVerilogHDLファイルmem.vを プロジェクトに追加し、この中に命令メモリimemの記述とともに 記述するとよいでしょう。 またこれに以下のようなプログラム(もっと長いプログラムでもよい)を 命令メモリimem内に記述して、これを実行させてみましょう。 (このプログラムでは、最終的にr1=1となることになる)
番地(iaddr) | 命令(idata) | 命令のニーモニック表記 |
0 | 0000 0001 | mov 1, r0 |
1 | 1000 0011 | mov r0, @3 |
2 | 1011 0011 | mov @3, r1 |
3 | 0110 0011 | jmp 3 |
データメモリの一部を、LEDなどのI/Oデバイスのように扱うことを 「メモリマップド(Memory-mapped I/O)」と呼びます。 ここでは、データメモリの特定の番地にデータを書き込むと、 その値が直接LEDに表示されるような回路dioを設計してみましょう。
といってもメモリマップドI/Oの出力側(CPUから書き込まれる側)は、 基本的にはデータメモリの書き込み部分と全く同じです。 例えば次のような回路をdio.vとしてプロジェクトに追加しておきます。
module dio(clk, wr, addr, din, d_led); input clk, wr; input [3:0] addr, din; output [3:0] d_led; reg [3:0] d_led; always @(posedge clk) begin if (wr == 1'b1) if (addr == 4'hf) d_led <= din; end endmodule中を読むとわかるとおり、addr=4'hf (2進数で1111、10進数で15)番地への 「書き込み」に対しては、その書き込んだデータを 出力d_ledに出力する(そしてその値を保持する)、という動作をするような 記述になっています。 CPU本体のほうでは、次のようにこの回路dioを呼び出しておき、 アドレス、データにはdmemと同様に、アドレスdaddrと CPUからdmemへ向かうデータと同じd2ioをつないでおきます。 出力d_ledは、wireとして宣言をしておきましょう。
wire [3:0] d_led; dio i2(clk, mem_wr, daddr, d2io, d_led);この出力d_ledは、7セグメントLEDやLEDのあいているところに assignでつないでおいて表示させることにします。
演習3-3 メモリマップドI/Oの回路を設計し、CPUとあわせて動作させてみましょう。 その動作を確認できる適当なプログラムをimemに格納させること。 またメモリマップドI/Oを拡張し、割り当て番地を変更したり 複数の書き込みデバイスや読み出しデバイスを設計して動作させてみましょう。
※ヒント:読み出しデバイスは、データメモリの特定のアドレスに
対する読み出しの際に、入力信号(例えばスイッチの値)を
読み出しデータd2cpuに返すようにすればよい。
ただしデータメモリdmemからCPUへ向かうデータd2cpuと、
このdioからCPUへ向かうデータd2cpuは同一の信号線であるため、
dmemとdioの両方がd2ioにデータを「出力」しようとすると、
「競合」(CPUへの読み出しデータ信号d2cpuに、データメモリdmemとこの読み出しデバイスの
2つが同時に値を出そうとすると、インプリメンテーション時にエラーが起こるか、
動作させられても最悪の場合故障する)することになる。
そのため、データメモリのd2cpuへの値の出力は
アドレスdaddrがデータメモリ対象の値のときだけ、とし、
それ以外のときは次のように「接続していない(高インピーダンスZ)」という値を
出力するようにする。
assign dout = (addr == 5)?(4'bZZZZ):(mem[addr]);この例では、C言語での「?:」演算子と同様に、 addr=5の場合は4ビットのZ(高インピーダンス)を、 それ以外の場合はデータメモリの内容をd2cpuへ出力するようにしている。 高インピーダンスの意味は、次の図のように考えるとよい。
assign dout = (addr == 5)?(....):(4'bZZZZ);