周辺機器を追加(UARTコントローラ)
#自作CPU #RISC-V #Verilog #ULX3S #FPGA #Odeeen
from マルチサイクル RISC-V CPU を作成したい
自作CPUで、Memory Mapped-IO を使って LED を点灯することができたので、次は UART で通信できるようにしたい。
使用するUARTコア
FPGAでUARTループバック回路を作成した でも使った osdvu を UART コアとして使用する。
freecores / osdvu (Open Source Documented UaRT)
https://github.com/freecores/osdvu
UARTのMMIO
UARTのMemory Mapped-IO(MMIO)まわりの仕様については、Zucker SOC を参考にするとよさそう(ZuckerSOCメモ の「UART関連」を参照)
アドレス 0xf0000000 の「UARTデータレジスタ」と、アドレス 0xf0000004の「UART制御レジスタ」の二つを用意。仕様は以下のとおり。
UARTのデータレジスタ
アドレス: 0xf0000000
送信したい場合は 0xf0000000 へ書き込む
受信したい場合は 0xf0000000 から読み込む
UARTの制御レジスタ
アドレス: 0xf0000004
読み込んだデータの意味
最下位ビット( uart_ctr[0] ):受信データの有無(未読の場合は1、読み込みを行うと1→0となる)
下位第二ビット( uart_ctr[1] ):データ送信中の場合は1、そうでない場合は0
実装はこんな感じ
mem_rdata[7:0] <= { 6'b0, uart0_txbusy, uart0_dr };
固定文字の送信
ひとまず固定文字列を送信できるようにする。
以下のようなサンプルプログラムを作成し、
code:verilog
mem6 = lui(10, 32'hf0000000 >> 12); // x10 = 0xf0001000
mem7 = addi(11, 0, 65); // x11 = 'A'
mem8 = sw(10, 11, 0); // M0xf0000000 = x11
mem9 = add(0, 0, 0); // nop
mem10 = jal(0, -4); // 無限ループ
UARTコアの tx_byte へ送信文字をセットし、transmit をアサートしてやる。
code:diff
diff --git a/Makefile b/Makefile
index a29ef31..a4d9a2d 100644
--- a/Makefile
+++ b/Makefile
@@ -11,11 +11,14 @@ ulx3s.bit: ulx3s_out.config
ulx3s_out.config: odeeen.json
nextpnr-ecp5 --85k --json odeeen.json --lpf rtl/ulx3s_v20.lpf --textcfg ulx3s_out.config
-odeeen.json: rtl/cpu.sv rtl/ulx3s_top.sv rtl/bram_controller.sv
- yosys -p "hierarchy -top ulx3s_top" -p "synth_ecp5 -json odeeen.json" rtl/cpu.sv rtl/bram_controller.sv rtl/ulx3s_top.sv
+odeeen.json: rtl/cpu.sv rtl/ulx3s_top.sv rtl/bram_controller.sv rtl/uart.v
+ yosys -p "hierarchy -top ulx3s_top" -p "synth_ecp5 -json odeeen.json" rtl/cpu.sv rtl/bram_controller.sv rtl/ulx3s_top.sv rtl/uart.v
prog: ulx3s.bit
- fujprog ulx3s.bit
+ fujprog -j SRAM ulx3s.bit
+
+prog_flash: ulx3s.bit
+ fujprog -j FLASH ulx3s.bit
test:
cd rtl && iverilog -g 2012 -s cpu_test cpu_test.sv cpu.sv bram_controller.sv && ./a.out
diff --git a/rtl/bram_controller.sv b/rtl/bram_controller.sv
index 1ea32fa..620a8af 100644
--- a/rtl/bram_controller.sv
+++ b/rtl/bram_controller.sv
@@ -66,6 +66,17 @@ module bram_controller(
// デバッグ
// mem20 = jal(0, 0); // 無限ループ
// mem24 = jal(0, 0); // 無限ループ
+
+ //----------------------------------------
+ // UART 送信テスト
+ //----------------------------------------
+ mem6 = lui(10, 32'hf0000000 >> 12); // x10 = 0xf0001000
+ mem7 = addi(11, 0, 65); // x11 = 'A'
+ mem8 = sw(10, 11, 0); // M0xf0000000 = x11
+ mem9 = add(0, 0, 0); // nop
+ mem10 = jal(0, -4); // 無限ループ
+
+
end
always_ff @(posedge clk) begin
diff --git a/rtl/ulx3s_top.sv b/rtl/ulx3s_top.sv
index 7c0830b..bc7b83d 100644
--- a/rtl/ulx3s_top.sv
+++ b/rtl/ulx3s_top.sv
@@ -4,7 +4,9 @@
module ulx3s_top(
input wire clk_25mhz,
input wire 6:0 btn,
- output wire 7:0 led
+ output wire 7:0 led,
+ output wire ftdi_rxd, // FPGA transmits to ftdi
+ input wire ftdi_txd // FPGA receives from ftdi
);
//------------------------------------------------------------------
@@ -67,6 +69,95 @@ module ulx3s_top(
logic 31:0 led_ctl_mem_rdata;
+ //------------------------------------------------------------------
+ // UART コントローラ
+ //------------------------------------------------------------------
+
+ uart uart_inst (
+ .clk(clk),
+ .rst(1'b0),
+ .rx(ftdi_txd),
+ .tx(ftdi_rxd),
+ .transmit(tx_trigger),
+ .tx_byte(tx_byte),
+ .received(received),
+ .rx_byte(rx_byte),
+ .is_receiving(is_receiving),
+ .is_transmitting(is_transmitting),
+ .recv_error(recv_error)
+ );
+
+ // UART コア用の信号線
+ logic tx_trigger; // Signal to start transmission
+ logic 7:0 tx_byte; // Byte to transmit
+ logic received; // Signal indicating a byte is received
+ logic 7:0 rx_byte; // Received byte
+ logic is_receiving; // Indicates receiving state
+ logic is_transmitting; // Indicates transmitting state
+ logic recv_error; // Indicates receive error
+
+ // TODO: 送信する文字は固定。あとで直す
+ assign tx_byte = 8'd65; // 'A'
+
+ // UART と CPU との通信用の信号線
+ logic uart_data_mem_ready;
+ logic 31:0 uart_data_mem_rdata;
+ logic uart_ctl_mem_ready;
+ logic 31:0 uart_ctl_mem_rdata;
+
+ assign uart_data_mem_valid = uart_data_en && mem_valid;
+ assign uart_ctl_mem_valid = uart_ctl_en && mem_valid;
+ // TODO: 今は送信にのみ対応。あとで受診にも対応する
+ assign uart_data_mem_ready = (tx_state_reg === TX_STATE_FINISH) ? 1'b1 : 1'b0;
+
+
+ // UART トランスミッタのステート
+ // 001: 待機
+ // 010: 送信開始
+ // 100: 送信中
+ typedef enum logic 2:0 {
+ TX_STATE_IDLE = 3'b001,
+ TX_STATE_START = 3'b010,
+ TX_STATE_FINISH = 3'b100
+ } tx_state_t;
+
+ tx_state_t tx_state_reg, tx_state_next;
+
+ always_ff @(posedge clk) begin
+ if (!reset_n) begin
+ tx_state_reg <= TX_STATE_IDLE;
+ end else begin
+ tx_state_reg <= tx_state_next;
+ end
+ end
+
+ always_comb begin
+ tx_trigger = 1'b0;
+
+ case (tx_state_reg)
+ TX_STATE_IDLE: begin
+ if (uart_data_mem_valid && mem_wstrb === 4'b1111) begin
+ // 送信の場合
+ tx_state_next = TX_STATE_START;
+ end else begin
+ tx_state_next = TX_STATE_IDLE;
+ end
+ end
+ TX_STATE_START: begin
+ tx_trigger = 1'b1;
+
+ tx_state_next = TX_STATE_FINISH;
+ end
+ TX_STATE_FINISH: begin
+ tx_state_next = TX_STATE_IDLE;
+ end
+ default: begin
+ tx_state_next = TX_STATE_IDLE;
+ end
+ endcase
+ end
+
+
//------------------------------------------------------------------
// 周辺機器との接続
//------------------------------------------------------------------
@@ -78,10 +169,17 @@ module ulx3s_top(
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 mem_ready = (bram_en) ? bram_mem_ready :
+ (led_ctl_en) ? led_ctl_mem_ready :
+ (uart_data_en) ? uart_data_mem_ready :
+ (uart_ctl_en) ? uart_ctl_mem_ready
+ : 1'b1; // 接続先のペリフェラルが存在しない場合は常に ready = 1 とする
+
+ assign mem_rdata = (bram_en) ? bram_mem_rdata :
+ (led_ctl_en) ? led_ctl_mem_rdata :
+ (uart_data_en) ? uart_data_mem_rdata :
+ (uart_ctl_en) ? uart_ctl_mem_rdata
+ : 32'h0;
//------------------------------------------------------------------
@@ -92,6 +190,7 @@ module ulx3s_top(
assign led = led_ctl_mem_rdata7:0;
// assign led = peek7:0;
+ // assign led = { uart_data_en, tx_trigger, uart_data_mem_valid, 2'b0, tx_state_reg };
endmodule
screen コマンドを使ってシリアルコンソールへ接続、「A」がすごい勢いで出力される。
code:sh
$ screen /dev/cu.usbserial-D00084 9600
https://gyazo.com/74d8d635059097ff45540f9dd283857e
UARTのループバック
受信も実装して、UARTのループバックをできるようにした。
キーボードを連打してると文字が化けるので、データ送信中に送信データレジスタが書き換えられてるのかも。送信中は送らないような putc 関数を書かねば。
https://gyazo.com/663c49866f99092efc42071354b5518a
code:diff
diff --git a/rtl/bram_controller.sv b/rtl/bram_controller.sv
index 620a8af..1aee49e 100644
--- a/rtl/bram_controller.sv
+++ b/rtl/bram_controller.sv
@@ -37,6 +37,16 @@ module bram_controller(
// メモリの初期化
initial begin
+ // レジスタの使い方
+ // x1: return address
+ // x3: Global Pointer
+ // x5: t0
+ // x6: t1
+ // x7: t2
+ // x9: s1 (== 1)
+ // x10: a0 / return value
+ // x11: a1
+
// デバッグ用の各種変数
mem0 = lui(10, 32'hf0001000 >> 12); // x10 = 0xf0001000
mem1 = addi(11, 0, 1);
@@ -68,14 +78,22 @@ module bram_controller(
// mem24 = jal(0, 0); // 無限ループ
//----------------------------------------
- // UART 送信テスト
+ // UART ループバック
+ //
+ // 未受信データがあったら読み込み、UART へ送り返す
//----------------------------------------
- mem6 = lui(10, 32'hf0000000 >> 12); // x10 = 0xf0001000
- mem7 = addi(11, 0, 65); // x11 = 'A'
- mem8 = sw(10, 11, 0); // M0xf0000000 = x11
- mem9 = add(0, 0, 0); // nop
- mem10 = jal(0, -4); // 無限ループ
-
+ mem6 = lui(10, 32'hf0000000 >> 12); // x10 = UARTデータレジスタアドレス
+ mem7 = addi(11, 10, 4); // x11 = UART制御レジスタアドレス
+ mem8 = lui(12, 32'hf0001000 >> 12); // x12 = LEDアドレス
+ mem9 = addi(1, 0, 1); // x1 = 1
+ // loop:
+ mem10 = lw(13, 11, 0); // UART 制御レジスタの値を読み込む
+ mem11 = beq(13, 1, 4); // 未受信データがある場合は、先へ進む
+ mem12 = jal(0, -4); // j loop
+ mem13 = lw(13, 10, 0); // UART の受信データを読み込み
+ mem14 = sw(10, 13, 0); // 受信データをループバックする
+ mem15 = sw(12, 13, 0); // 受信データを LED へ出力
+ mem16 = jal(0, -12); // j loop
end
diff --git a/rtl/ulx3s_top.sv b/rtl/ulx3s_top.sv
index bc7b83d..6c020ec 100644
--- a/rtl/ulx3s_top.sv
+++ b/rtl/ulx3s_top.sv
@@ -96,9 +96,6 @@ module ulx3s_top(
logic is_transmitting; // Indicates transmitting state
logic recv_error; // Indicates receive error
- // TODO: 送信する文字は固定。あとで直す
- assign tx_byte = 8'd65; // 'A'
-
// UART と CPU との通信用の信号線
logic uart_data_mem_ready;
logic 31:0 uart_data_mem_rdata;
@@ -107,9 +104,16 @@ module ulx3s_top(
assign uart_data_mem_valid = uart_data_en && mem_valid;
assign uart_ctl_mem_valid = uart_ctl_en && mem_valid;
- // TODO: 今は送信にのみ対応。あとで受診にも対応する
assign uart_data_mem_ready = (tx_state_reg === TX_STATE_FINISH) ? 1'b1 : 1'b0;
+ // 送受信データ置き場
+ assign tx_byte = mem_wdata7:0; // 送信データ
+ assign uart_data_mem_rdata = { 24'h0, rx_byte }; // 受信データ
+ assign uart_ctl_mem_rdata = { 6'b0, is_transmitting, unread_reg }; // { 000000, 送信中, 未読有無 }
+
+ // 未読データの有無
+ logic unread_reg, unread_next;
+ assign unread_next = (received) ? 1'b1 : unread_reg;
// UART トランスミッタのステート
// 001: 待機
@@ -126,8 +130,15 @@ module ulx3s_top(
always_ff @(posedge clk) begin
if (!reset_n) begin
tx_state_reg <= TX_STATE_IDLE;
+
+ unread_reg <= 1'b0;
end else begin
tx_state_reg <= tx_state_next;
+
+ if (uart_data_mem_valid && mem_wstrb === 4'b0000)
+ unread_reg <= 1'b0;
+ else
+ unread_reg <= unread_next;
end
end
@@ -139,6 +150,10 @@ module ulx3s_top(
if (uart_data_mem_valid && mem_wstrb === 4'b1111) begin
// 送信の場合
tx_state_next = TX_STATE_START;
+ end else if (uart_data_mem_valid && mem_wstrb === 4'b0000) begin
+ // 受信の場合
+ // ready だけ返せばいいので、直接 TX_STATE_FINISH に遷移
+ tx_state_next = TX_STATE_FINISH;
end else begin
tx_state_next = TX_STATE_IDLE;
end
@@ -190,7 +205,7 @@ module ulx3s_top(
assign led = led_ctl_mem_rdata7:0;
// assign led = peek7:0;
- // assign led = { uart_data_en, tx_trigger, uart_data_mem_valid, 2'b0, tx_state_reg };
+ // assign led = uart_ctl_mem_rdata;
endmodule