UARTで送受信

昨日の夜、"Hello World"を送信する回路がちゃんと動いて、安心して寝た。今日はPC -> FPGAの受信を書き、とりあえずこちらが送った文字をそのまま返すだけの回路を作った。少しは慣れてきたのか、シミュレーションでデバッグしたあと実機に焼いたら即動いたのでガッツポーズしてしまった。というのも、数日前まではシミュレーションで動くように見えてもビットストリームを作れないような回路とか、実機では動かないような回路を書いてしまっていたので……。

今日、途中でシミュレーションしようとしても「executing analysis and compilation step」のダイアログが出たまま始まらなくなってしまい、何かおかしくなったのかとシミュレータを閉じたり、Vivadoを閉じたり、Windowsを再起動しても直らなかったのでほげ~と言いながらググっていたら以下のような回答が出てきた。

forums.xilinx.com

これにある通り、tclコンソールにreset_simulation -simset sim_1 -mode behavioralと入力すると動くようになった。何だったんだ。

しばらくちゃんと本を読んだり他人のコードを読んだりしていて、今まで(といっても今日で書き始めて1週間なのだが)regを意味もなく多用していたことに気づいた。その瞬間の入力の組み合わせだけで問題ない部分はwireでよい。ていうかなんでregばっかり使ってたんだろうな。

ところで、wireに対してifを使おうとしたときfunctionまみれになるのはどうしたらいいんだろう。functionで一つの変数しか返していないので無駄に分割された大量のコードが並ぶようになってしまった。もちろん、functionの返り値をassign {val1, val2} = next_value(...)のようにしてパック展開(?)で受ければよいのだが、これはこれでまた若干面倒くさい。何かいい書き方があるのを知らないだけな気がするのだけど。SystemVerilogではalways_combが使えるのだが。

次にすることを考える。とりあえず文字列を送受信できるようになったので、何かしらの簡単な計算をする回路を作れるはずだ。例えばmd5sumとかはできるだろう。とはいえ一足飛びにそこまで行くのは大変そうなので、まずは単に文字列を覚えてあとで返すようなものを作った方がよさそう。ヴィジュネル暗号とかだと少し楽しいだろうか? もう少し先の話としては、Brainfuckインタプリタなどは楽しそうだしやってみたいと思っていた。

その前に少しリファクタリングと機能追加をした方がいいかもしれない。例えば、送受信回路にバッファ用のメモリを一枚噛ませることで、処理を中断させずに送信用のデータをためたり、読み込みが終わっているかどうか心配せずに受信データを更新したりできるようにしておきたい。まあそのメモリが溢れたら止めざるを得ないが、ないよりましだろう。特に今は受信データは次のデータが来た瞬間に問答無用で消えてしまうので、かなりタイミングがシビアになっている。これはよくない。

ここまでとりあえずVerilogから勉強していくか、と思ってVerilogの本を読んでいたが、普通にSystemVerilogの文法のさわりだけでも知っているとパク……参考にできるコードの幅が広がるので両方やったほうがよさそう。最終的にChiselまで触る気ではいる(これはRustを勉強する前にC++を勉強する前にCを勉強するようなもの(メリットもあるがコストも高い)なのではないかという気はしたものの、性格的にはその方が自分には合っているのではないかという気がしている。それだけのコストをかけるだけの余裕があるかというのは年々怪しくなっているが……)。

Vivadoで使えるテキストエディタGVimがあったのでGVimを導入して.vimrcなどを設定したが、vivadoが出してくれるエラーが表示できない。そういうプラグインがあるだろうと思ったが、すぐには見つからなかった。あとで気合を入れて探してみるか。

そうそう、それからコーディングスタイルをとっとと固めておいた方がいいな。どういう風に書くのが普通なのかあまりよく知らないので今はなんか適当に書いてる。

 module top(CLK100MHZ, BUTTON, uart_rxd_out, uart_txd_in, led0, led1);
     input  wire      CLK100MHZ;
     input  wire      BUTTON;
     input  wire      uart_txd_in;
     output wire      uart_rxd_out;
     output wire[2:0] led0;
     output wire[2:0] led1;
 
     wire      uart_send_enable;
     wire[7:0] uart_send_data;
     wire      uart_sending;
 
     uart_send send(
         .clock(CLK100MHZ),
         .reset(BUTTON),
         .uart_enable(uart_send_enable),
         .uart_data(uart_send_data),
         .uart_busy(uart_sending),
         .uart_tx(uart_rxd_out),
         .led(led0)
         );
 
     assign led1 = 3'b000;
     wire[7:0] uart_recv_data;
     wire      uart_receiving;
     wire      uart_recv_okay;
 
     uart_recv recv(
         .clock(CLK100MHZ),
         .reset(BUTTON),
         .uart_data(uart_recv_data),
         .uart_busy(uart_receiving),
         .uart_okay(uart_recv_okay),
         .uart_rx(uart_txd_in),
         .led(led1)
         );

    reg[7:0] data;
    reg data_sent;
    wire has_data = (~uart_receiving) && uart_recv_okay;

    assign uart_send_data = data;

    parameter IDLE_STATE = 0;
    parameter SEND_STATE = 1;
    parameter WAIT_STATE = 2;
    reg [1:0]   state = IDLE_STATE;
    wire[1:0] w_state;

    assign uart_send_enable = state == SEND_STATE;

    function [1:0] next_state(
        input[1:0] state,
        input      uart_sending,
        input      has_data,
        input      data_sent
        );
        if (state == IDLE_STATE) begin
            if (has_data && (~data_sent)) begin
                if (uart_sending) begin
                    next_state = WAIT_STATE;
                end else begin
                    next_state = SEND_STATE;
                end
            end else begin
                next_state = IDLE_STATE;
            end
        end else if (state == WAIT_STATE) begin
            if (uart_sending) begin
                next_state = WAIT_STATE;
            end else begin
                next_state = SEND_STATE;
            end
        end else begin // SEND_STATE
            next_state = IDLE_STATE;
        end
    endfunction
    assign w_state = next_state(state, uart_sending, has_data, data_sent);
    
    always @(posedge CLK100MHZ) begin
        if (BUTTON) begin
            state     <= IDLE_STATE;
            data      <= 8'b0;
            data_sent <= 1'b0;
        end else begin
            state <= w_state;
            
            if (uart_recv_okay) begin
                data <= uart_recv_data;
            end else begin
                data <= 8'b0;
            end
            
            if (uart_receiving) begin
                data_sent <= 1'b0;
            end else if (state == SEND_STATE) begin
                data_sent <= 1'b1;
            end
        end
    end
 endmodule

 module uart_send(clock, reset, uart_enable, uart_data, uart_busy, uart_tx, led);
     input  wire       clock;
     input  wire       reset;
     input  wire       uart_enable;
     input  wire [7:0] uart_data;
 
     output wire       uart_busy;
     output wire       uart_tx;
     output wire [2:0] led;
 
     parameter CLK_PER_BIT = 868; // 100 MHz / 115200 Hz
 
     parameter IDLE_STATE = 0;
     parameter SEND_STATE = 1;
 
     reg  state = IDLE_STATE;
     wire w_state;
 
     reg [9:0] data = 10'b1111111111;
     wire[9:0] w_data;
 
     reg [9:0] clock_counter = 10'b0;
     wire[9:0] w_clock_counter;
 
     reg [3:0] bit_counter = 4'b0;
     wire[3:0] w_bit_counter;
 
     assign uart_tx   = data[0];
     assign uart_busy = (state == IDLE_STATE) ? 1'b0 : 1'b1;
 
     assign led[2:0]  = (state == IDLE_STATE) ? 3'b001 : 3'b010;
 
     // ----------------------------------------------------------------------
     // update state
 
     function [0:0] next_state(
         input      state,
         input      enable,
         input[9:0] clock_counter,
         input[3:0] bit_counter
         );
 
         if (state == IDLE_STATE) begin
             if(enable) begin
                 next_state = SEND_STATE;
             end else begin
                 next_state = IDLE_STATE;
             end
         end else begin // SEND_STATE
             if (clock_counter == CLK_PER_BIT-1) begin
                 if (bit_counter == 4'd9) begin
                     next_state = IDLE_STATE;
                 end else begin
                     next_state = SEND_STATE;
                 end
             end else begin
                next_state = SEND_STATE;
             end
         end
     endfunction
 
     assign w_state = next_state(state, uart_enable, clock_counter, bit_counter);
 
     // ----------------------------------------------------------------------
     // update clock counter
 
     function [9:0] next_clock_counter(
         input      state,
         input[9:0] clock_counter
         );
 
         if (state == IDLE_STATE) begin
             next_clock_counter = 10'b0;
         end else begin
             if (clock_counter == CLK_PER_BIT-1) begin
                 next_clock_counter = 10'b0;
             end else begin
                 next_clock_counter = clock_counter + 1;
             end
         end
     endfunction
 
     assign w_clock_counter = next_clock_counter(state, clock_counter);
 
     // ----------------------------------------------------------------------
     // update number of bits sent
 
     function [3:0] next_bit_counter(
         input      state,
         input[9:0] clock_counter,
         input[3:0] bit_counter
         );
 
         if(state == IDLE_STATE) begin
             next_bit_counter = 4'b0;
         end else begin
             if (clock_counter == CLK_PER_BIT-1) begin
                 if (bit_counter == 4'd9) begin
                     next_bit_counter = 4'd0;
                 end else begin
                     next_bit_counter = bit_counter + 1;
                 end
             end else begin
                 next_bit_counter = bit_counter;
             end
         end
     endfunction
 
     assign w_bit_counter = next_bit_counter(state, clock_counter, bit_counter);
 
     // ----------------------------------------------------------------------
     // update data to send
 
     function [9:0] next_data(
         input      state,
         input      enable,
         input[7:0] uart_data,
         input[9:0] data,
         input[9:0] clock_counter
         );
 
         if (state == IDLE_STATE) begin
             if (enable) begin
                 next_data = {1'b1, uart_data, 1'b0};
             end else begin
                 next_data = data;
             end
         end else begin
             if (clock_counter == CLK_PER_BIT - 1) begin
                 next_data = {1'b1, data[9:1]};
             end else begin
                 next_data = data;
             end
         end
     endfunction
     assign w_data = next_data(state, uart_enable, uart_data, data, clock_counter);
 
     // ----------------------------------------------------------------------
     // update registers
 
     always @(posedge clock) begin
         if (reset) begin
             state         <= IDLE_STATE;
             clock_counter <= 10'b0;
             bit_counter   <=  4'b0;
             data          <= 10'b1111111111;
         end else begin
             state         <= w_state;
             clock_counter <= w_clock_counter;
             bit_counter   <= w_bit_counter;
             data          <= w_data;
         end
     end
 endmodule

module uart_recv(clock, reset, uart_data, uart_busy, uart_okay, uart_rx, led);
    input  wire       clock;
    input  wire       reset;
    output wire [7:0] uart_data;

    output wire       uart_busy;
    output wire       uart_okay;
    input  wire       uart_rx;
    output wire [2:0] led;

    parameter CLK_PER_BIT = 868; // 100 MHz / 115200 Hz

    parameter IDLE_STATE = 0;
    parameter CHCK_STATE = 1; // checking start bit
    parameter RECV_STATE = 2;
    parameter STOP_STATE = 3; // receiving stop bit

    reg [1:0] state = IDLE_STATE;
    wire[1:0] w_state;

    reg [9:0] clock_counter = 10'b0;
    wire[9:0] w_clock_counter;

    reg [3:0] bit_counter = 4'b0;
    wire[3:0] w_bit_counter;

    reg [9:0] data = 10'b1111111111;
    wire[9:0] w_data;

    assign uart_data = data[8:1]; // except start/stop bits
    assign uart_okay = (data[0:0] == 1'b0) && (data[9:9] == 1'b1); // start with 0, end with 1
    assign uart_busy = (state == RECV_STATE) ? 1'b1 : 1'b0;
    assign led[2:0]  = (state == IDLE_STATE) ? 3'b001 :
                       (state == CHCK_STATE) ? 3'b010 :
                       (state == RECV_STATE) ? 3'b100 : 3'b111;

    // ----------------------------------------------------------------------
    // update state

    wire rx_is_zero;
    assign rx_is_zero = (uart_rx == 1'b0);

    function [1:0] next_state(
        input[1:0] state,
        input      uart_rx,
        input      rx_is_zero,
        input[9:0] clock_counter,
        input[3:0] bit_counter
        );

        if (state == IDLE_STATE) begin
            if(rx_is_zero) begin
                next_state = CHCK_STATE;
            end else begin
                next_state = IDLE_STATE;
            end
        end else if (state == CHCK_STATE) begin
            if (clock_counter == (CLK_PER_BIT / 2) - 1) begin
                if (rx_is_zero) begin
                    next_state = RECV_STATE;
                end else begin
                    next_state = IDLE_STATE;
                end
            end else begin
                next_state = CHCK_STATE;
            end
        end else if (state == RECV_STATE) begin
            if (clock_counter == CLK_PER_BIT-1) begin
                if (bit_counter == 4'd8) begin
                    next_state = STOP_STATE;
                end else begin
                    next_state = RECV_STATE;
                end
            end else begin
               next_state = RECV_STATE;
            end
        end else begin // STOP_STATE
            if (clock_counter == (CLK_PER_BIT / 2) - 1) begin
                next_state = IDLE_STATE;
            end else begin
                next_state = STOP_STATE;
            end
        end
    endfunction

    assign w_state = next_state(state, uart_rx, rx_is_zero, clock_counter, bit_counter);

    // ----------------------------------------------------------------------
    // update clock counter

    function [9:0] next_clock_counter(
        input[1:0] state,
        input[9:0] clock_counter
        );

        if (state == IDLE_STATE) begin
            next_clock_counter = 10'b0;
        end else if(state == CHCK_STATE) begin
            if (clock_counter == CLK_PER_BIT / 2 - 1) begin
                next_clock_counter = 10'b0;
            end else begin
                next_clock_counter = clock_counter + 1;
            end
        end else begin
            if (clock_counter == CLK_PER_BIT-1) begin
                next_clock_counter = 10'b0;
            end else begin
                next_clock_counter = clock_counter + 1;
            end
        end
    endfunction

    assign w_clock_counter = next_clock_counter(state, clock_counter);

    // ----------------------------------------------------------------------
    // update number of bits sent

    function [3:0] next_bit_counter(
        input[1:0] state,
        input[9:0] clock_counter,
        input[3:0] bit_counter
        );

        if(state == IDLE_STATE) begin
            next_bit_counter = 4'b0;
        end else if(state == CHCK_STATE) begin
            next_bit_counter = 4'b0;
        end else begin
            if (clock_counter == CLK_PER_BIT-1) begin
                if (bit_counter == 4'd9) begin
                    next_bit_counter = 4'd0;
                end else begin
                    next_bit_counter = bit_counter + 1;
                end
            end else begin
                next_bit_counter = bit_counter;
            end
        end
    endfunction

    assign w_bit_counter = next_bit_counter(state, clock_counter, bit_counter);

    // ----------------------------------------------------------------------
    // update received data

    function [9:0] next_data(
        input[1:0] state,
        input      uart_rx,
        input[9:0] data,
        input[9:0] clock_counter
        );

        if (state == IDLE_STATE) begin
            next_data = data;
        end else if (state == CHCK_STATE) begin
            if (uart_rx == 1'b0) begin
                next_data = 10'b0111111111;
            end else begin
                next_data = data;
            end
        end else if (state == RECV_STATE) begin
            if (clock_counter == CLK_PER_BIT - 1) begin
                next_data = {uart_rx, data[9:1]};
            end else begin
                next_data = data;
            end
        end else begin
            next_data = data;
        end
    endfunction
    assign w_data = next_data(state, uart_rx, data, clock_counter);

    // ----------------------------------------------------------------------
    // update registers

    always @(posedge clock) begin
        if (reset) begin
            state         <= IDLE_STATE;
            clock_counter <= 10'b0;
            bit_counter   <=  4'b0;
            data          <= 10'b1111111111;
        end else begin
            state         <= w_state;
            clock_counter <= w_clock_counter;
            bit_counter   <= w_bit_counter;
            data          <= w_data;
        end
    end
endmodule