周辺機器を追加(LEDコントローラ)
#自作CPU #RISC-V #Verilog #ULX3S #FPGA #Odeeen
from マルチサイクル RISC-V CPU を作成したい
FPGA上でCPUが動くようになったので、LEDやUARTなどの周辺機器を追加していきたい。
手始めに Odeeen から LED を点灯できるようにする。
実装
LEDコントローラを雑に作成し、アドレス 0xf0001000 へ書き込んだ値を LED へ出力できるようにする。
データバスには「メモリコントローラ」と「LEDコントローラ」のふたつがぶら下がってるので、指定したアドレスによって片方のコントローラのみが有効になるような制御も追加する。
code:verilog
// メモリマップ
assign bram_en = (mem_addr < 8192) ? 1'b1 : 1'b0;
assign uart_data_en = (mem_addr == 32'hf0000000) ? 1'b1 : 1'b0;
assign uart_ctl_en = (mem_addr == 32'hf0000004) ? 1'b1 : 1'b0;
assign led_ctl_en = (mem_addr == 32'hf0001000) ? 1'b1 : 1'b0;
CPUから周辺機器への信号線はCPUから出ている信号をそのまま周辺機器のコントローラへ繋いでいいが、周辺機器からCPUへ向かう信号(readyとrdata)は、選択してる周辺機器の信号のみがCPUへ取り込まれるようマルチプレクサで制御する必要がある。
code:verilog
// 周辺機器 => CPU
assign mem_ready = (bram_en) ? bram_mem_ready :
(led_ctl_en) ? led_ctl_mem_ready : 1'b0;
assign mem_rdata = (bram_en) ? bram_mem_rdata :
(led_ctl_en) ? led_ctl_mem_rdata : 32'h0;
諸々修正したものがこちら。
code:ulx3s_top.sv
//------------------------------------------------------------------
// ULX3S 上で Odeeen CPU を動かすためのトップモジュール
//------------------------------------------------------------------
module ulx3s_top(
input wire clk_25mhz,
input wire 6:0 btn,
output wire 7:0 led
);
// CPU のインスタンス
cpu cpu_inst(
.clk(clk),
.reset_n(reset_n),
.mem_valid(mem_valid),
.mem_instr(mem_instr),
.mem_ready(mem_ready),
.mem_addr(mem_addr),
.mem_wdata(mem_wdata),
.mem_wstrb(mem_wstrb),
.mem_rdata(mem_rdata),
.peek(peek)
);
logic clk, reset_n;
logic mem_valid, mem_ready, mem_instr;
logic 31:0 mem_addr, mem_wdata, mem_rdata;
logic 3:0 mem_wstrb;
// デバッグ用の信号線
logic 31:0 peek;
// メモリコントローラのインスタンス
bram_controller bram_ctl (
.clk(clk),
.reset_n(reset_n),
.mem_valid(bram_en && mem_valid),
.mem_ready(bram_mem_ready),
.mem_addr(mem_addr),
.mem_wdata(mem_wdata),
.mem_wstrb(mem_wstrb),
.mem_rdata(bram_mem_rdata)
);
logic bram_mem_ready;
logic 31:0 bram_mem_rdata;
// LED コントローラのインスタンス
led_controller led_ctl (
.clk(clk),
.reset_n(reset_n),
.mem_valid(led_ctl_en && mem_valid),
.mem_ready(led_ctl_mem_ready),
.mem_addr(mem_addr),
.mem_wdata(mem_wdata),
.mem_wstrb(mem_wstrb),
.mem_rdata(led_ctl_mem_rdata)
);
logic led_ctl_mem_ready;
logic 31:0 led_ctl_mem_rdata;
//------------------------------------------------------------------
// 周辺機器との接続
//------------------------------------------------------------------
// メモリマップ
assign bram_en = (mem_addr < 8192) ? 1'b1 : 1'b0;
assign uart_data_en = (mem_addr == 32'hf0000000) ? 1'b1 : 1'b0;
assign uart_ctl_en = (mem_addr == 32'hf0000004) ? 1'b1 : 1'b0;
assign led_ctl_en = (mem_addr == 32'hf0001000) ? 1'b1 : 1'b0;
// 周辺機器 => CPU
assign mem_ready = (bram_en) ? bram_mem_ready :
(led_ctl_en) ? led_ctl_mem_ready : 1'b0;
assign mem_rdata = (bram_en) ? bram_mem_rdata :
(led_ctl_en) ? led_ctl_mem_rdata : 32'h0;
//------------------------------------------------------------------
// 外部信号線との接続
//------------------------------------------------------------------
assign clk = clk_25mhz;
assign reset_n = btn0; // btn0 は押すとデアサートされる
assign led = led_ctl_mem_rdata7:0;
// assign led = peek7:0;
endmodule
// LED コントローラを雑に実装
module led_controller (
input wire clk,
input wire reset_n,
input wire mem_valid,
output logic mem_ready,
input wire 31:0 mem_addr, // 使わない
input wire 31:0 mem_wdata,
input wire 3:0 mem_wstrb, // 0'b1111 の場合は書き込み
output logic 31:0 mem_rdata,
);
logic 7:0 led_data_reg;
always_ff @(posedge clk) begin
if (!reset_n) begin
led_data_reg <= 8'b0;
end else begin
if (mem_valid && (mem_wstrb == 4'b1111)) begin
led_data_reg <= mem_wdata7:0;
end
end
end
assign mem_rdata = { 25'b0, led_data_reg };
assign mem_ready = 1'b1;
endmodule
LEDコントローラを追加したら、bram_controller のプログラムを以下のように修正し、LED へ 1001_1001 を出力してみる。
code:bram_controller.sv
// メモリの初期化
initial begin
// LED へ 0b1001_1001 を表示する
mem0 = addi(1, 0, 8'b10011001); // x1 = 0b10011001
mem1 = lui(2, 32'hf0001000 >> 12); // x2 = 0xf0001000
mem2 = sw(2, 1, 0); // Mx2+0 = x1
mem3 = jal(0, 0); // jal x0, 0 (無限ループ)
end
動かしてみる。LEDにちゃんと 1001_1001 が表示されている。
https://gyazo.com/fdf84b67d0635acb07b3145f31546fca
もうちょっと複雑なプログラムを動かしてみる
code:bram_controller.sv
// メモリの初期化
initial begin
// デバッグ用の各種変数
mem0 = lui(10, 32'hf0001000 >> 12); // x10 = 0xf0001000
mem1 = addi(11, 0, 1);
mem2 = addi(12, 0, 2);
mem3 = addi(13, 0, 4);
mem4 = addi(14, 0, 8);
mem5 = addi(15, 0, 16);
//----------------------------------------
// 1 から 10 までの合計を LED 出力するプログラム
//----------------------------------------
mem6 = sw(10, 11, 0); // LED に 1 を点灯
mem7 = addi(1, 0, 0); // sum = 0
mem8 = addi(2, 0, 10); // n = 10
// loop:
mem9 = sw(10, 12, 0); // LED に 2 を点灯
mem10 = beq(2, 0, 8); // if (n == 0) break
mem11 = add(1, 1, 2); // sum = sum + n
mem12 = addi(2, 2, -1); // n = n - 1
mem13 = jal(0, -8); // jump loop
// break:
mem14 = sw(10, 13, 0); // LED に 3 を点灯
mem15 = sw(10, 1, 0); // Mx2+0 = x1
mem16 = add(0, 0, 0); // nop
mem17 = jal(0, 0); // jal x0, 0 (無限ループ)
// デバッグ
// mem20 = jal(0, 0); // 無限ループ
// mem24 = jal(0, 0); // 無限ループ
end
1から10までの合計 55 がLEDに出力されている
https://gyazo.com/23c912a02d920139ab2e64361a782ae8