| // ===================================================================================== | |
| // (C) COPYRIGHT 2016 YongaTek (Yonga Technology Microelectronics) | |
| // All rights reserved. | |
| // This file contains confidential and proprietary information of YongaTek and | |
| // is protected under international copyright and other intellectual property laws. | |
| // ===================================================================================== | |
| // Project : GCU | |
| // File ID : %% | |
| // Design Unit Name : Modbus_Top.vhd | |
| // Description : Modbus Top | |
| // Comments : | |
| // Revision : %% | |
| // Last Changed Date : %% | |
| // Last Changed By : %% | |
| // Designer | |
| // Name : Burak Yakup Çakar | |
| // E-mail : burak.cakar@yongatek.com | |
| // ===================================================================================== | |
| module Modbus_Top #( | |
| parameter device_id = 8'h01, // Device address is 0x01 | |
| parameter clk_freq = 40000000, // 40 MHz clock frequency | |
| parameter baud_rate = 115200 // 115200 Baud Rate | |
| ) | |
| ( | |
| // Clock, reset and enable pins | |
| input i_clk, | |
| input i_rst, | |
| // Interface with register space | |
| output reg [7:0] o_mem_addr, | |
| output reg o_mem_wren, | |
| output reg o_mem_rden, | |
| input [15:0] i_mem_dout, | |
| output [15:0] o_mem_din, | |
| input i_mem_wrready, | |
| // UART interface | |
| input i_rx, | |
| output o_tx | |
| ); | |
| // State declaration | |
| localparam IDLE = 3'b000; | |
| localparam RECEIVE_REQUEST = 3'b001; | |
| localparam CHECK_ERRORS = 3'b010; | |
| localparam MEM_WRITE = 3'b011; | |
| localparam MEM_READ = 3'b100; | |
| localparam SEND_RESPONSE = 3'b101; | |
| localparam clkdiv = clk_freq / baud_rate; // Clocks per bit for specified Baud Rate | |
| localparam timeout_count = clkdiv * 4 * (8 + 1 + 1); // Wait for 4 bytes interval since the last data received. Baud: 115200. Clock: 100 MHz | |
| wire i_enable = 1'b1; // Enable all submodules and this module | |
| // UART controller signals | |
| reg [7:0] uart_tx_data; | |
| wire uart_tx_ready; | |
| reg uart_tx_wren; | |
| wire [7:0] uart_rx_data; | |
| wire uart_rx_ready; | |
| reg uart_rx_rden; | |
| // 16 bit CRC signals | |
| reg crc_soft_rst; | |
| reg [7:0] crc_data; | |
| reg crc_start; | |
| wire [15:0] crc_crc16; | |
| wire crc_done; | |
| // FIFO signals | |
| reg fifo_soft_rst; | |
| wire [15:0] fifo_din; | |
| wire [15:0] fifo_dout; | |
| reg fifo_re; | |
| reg fifo_we; | |
| // Modbus frame fields | |
| reg [7:0] id; | |
| reg [7:0] func; | |
| reg [15:0] start_addr; | |
| reg [15:0] quantity; | |
| reg [7:0] byte_count; | |
| reg [15:0] crc16; | |
| reg [7:0] errcode; | |
| // Receive state flags | |
| reg receive_func; | |
| reg receive_start_addr; | |
| reg receive_quantity; | |
| reg receive_byte_count; | |
| reg receive_data; | |
| reg receive_crc; | |
| // Send state flags | |
| reg send_id; | |
| reg send_func; | |
| reg send_start_addr; | |
| reg send_quantity; | |
| reg send_byte_count; | |
| reg send_data; | |
| reg send_crc; | |
| reg send_errcode; | |
| // UART RX data queue | |
| reg [7:0] uart_rx_data_q0; | |
| reg [7:0] uart_rx_data_q1; | |
| reg [15:0] fifo_din_reg; | |
| reg fifo_data_ready; | |
| // Enables CRC computation | |
| reg crc_enable; | |
| // Data byte counter | |
| reg [7:0] byte_counter; | |
| reg [15:0] timeout_counter; | |
| reg [2:0] state; | |
| // Modbus UART controller instantiation | |
| Modbus_UART_Controller #(clkdiv) Modbus_UART_Controller_inst ( | |
| .i_clk(i_clk), | |
| .i_rst(i_rst), | |
| .i_enable(i_enable), | |
| .i_tx_data(uart_tx_data), | |
| .o_tx_ready(uart_tx_ready), | |
| .i_tx_wren(uart_tx_wren), | |
| .o_rx_data(uart_rx_data), | |
| .o_rx_ready(uart_rx_ready), | |
| .i_rx_rden(uart_rx_rden), | |
| .i_rx(i_rx), | |
| .o_tx(o_tx) | |
| ); | |
| // CRC16 instantiation | |
| Modbus_CRC16 Modbus_CRC16_inst( | |
| .i_clk(i_clk), | |
| .i_rst(i_rst | crc_soft_rst), | |
| .i_enable(i_enable), | |
| .i_data(crc_data), | |
| .i_start(crc_start), | |
| .o_crc16(crc_crc16), | |
| .o_done(crc_done) | |
| ); | |
| // CoreFIFO instantiation | |
| fifo #(.ADDR_W(7), .DATA_W(16), .BUFF_L(128)) | |
| fifo_inst( | |
| .clk(i_clk), | |
| .n_reset(~(i_rst | fifo_soft_rst)), | |
| .wr_en(fifo_we), | |
| .data_in(fifo_din), | |
| .rd_en(fifo_re), | |
| .data_out(fifo_dout), | |
| .data_count(), | |
| .empty(), | |
| .full(), | |
| .almst_empty(), | |
| .almst_full(), | |
| .err() | |
| ); | |
| // FIFO data output is directly wired to memory | |
| assign o_mem_din = fifo_dout; | |
| // FIFO data input is connected to a register when receiving write requests, | |
| // it is directly wired to memory othervise | |
| assign fifo_din = (state == RECEIVE_REQUEST) ? fifo_din_reg : i_mem_dout; | |
| always @(posedge i_clk) begin | |
| if (i_rst) begin | |
| o_mem_addr <= 8'h00; | |
| o_mem_wren <= 1'b0; | |
| o_mem_rden <= 1'b0; | |
| uart_tx_data <= 8'h00; | |
| uart_tx_wren <= 1'b0; | |
| uart_rx_rden <= 1'b0; | |
| crc_soft_rst <= 1'b0; | |
| crc_data <= 8'h00; | |
| crc_start <= 1'b0; | |
| fifo_soft_rst <= 1'b0; | |
| fifo_din_reg <= 16'h0000; | |
| fifo_re <= 1'b0; | |
| fifo_we <= 1'b0; | |
| id <= 8'h00; | |
| func <= 8'h00; | |
| start_addr <= 16'h0000; | |
| quantity <= 16'h0000; | |
| crc16 <= 16'h0000; | |
| errcode <= 8'h00; | |
| receive_func <= 1'b0; | |
| receive_start_addr <= 1'b0; | |
| receive_quantity <= 1'b0; | |
| receive_byte_count <= 1'b0; | |
| receive_data <= 1'b0; | |
| receive_crc <= 1'b0; | |
| send_id <= 1'b0; | |
| send_func <= 1'b0; | |
| send_start_addr <= 1'b0; | |
| send_quantity <= 1'b0; | |
| send_byte_count <= 1'b0; | |
| send_data <= 1'b0; | |
| send_crc <= 1'b0; | |
| send_errcode <= 1'b0; | |
| uart_rx_data_q0 <= 8'h00; | |
| uart_rx_data_q1 <= 8'h00; | |
| crc_enable <= 1'b0; | |
| byte_counter <= 8'h00; | |
| fifo_data_ready <= 1'b0; | |
| timeout_counter <= 16'h0000; | |
| state <= IDLE; | |
| end | |
| else begin | |
| if (i_enable) begin | |
| // These signals will be reset when not overrode | |
| fifo_soft_rst <= 1'b0; | |
| fifo_re <= 1'b0; | |
| fifo_we <= 1'b0; | |
| crc_soft_rst <= 1'b0; | |
| crc_start <= 1'b0; | |
| uart_rx_rden <= 1'b0; | |
| uart_tx_wren <= 1'b0; | |
| case (state) | |
| IDLE : begin | |
| if (uart_rx_ready && ~uart_rx_rden) begin // Read and store RX data | |
| uart_rx_rden <= 1'b1; | |
| uart_rx_data_q0 <= uart_rx_data; | |
| uart_rx_data_q1 <= uart_rx_data_q0; | |
| crc_data <= uart_rx_data_q1; | |
| id <= uart_rx_data; | |
| end | |
| else if (uart_rx_ready && uart_rx_rden) begin // Change state with id check | |
| errcode <= (id != device_id) ? 8'hff : 8'h00; | |
| receive_func <= 1'b1; | |
| state <= RECEIVE_REQUEST; | |
| end | |
| else state <= IDLE; | |
| end | |
| RECEIVE_REQUEST : begin | |
| if (uart_rx_ready && ~uart_rx_rden) begin // Read and store RX data | |
| uart_rx_rden <= 1'b1; | |
| uart_rx_data_q0 <= uart_rx_data; | |
| uart_rx_data_q1 <= uart_rx_data_q0; | |
| crc_data <= uart_rx_data_q1; | |
| crc_start <= (crc_enable) ? 1'b1 : 1'b0; | |
| timeout_counter <= 16'h0000; | |
| // Receive state flags determine where RX data will be written | |
| if (receive_func) begin | |
| func <= uart_rx_data; | |
| crc_enable <= 1'b1; | |
| end | |
| else if (receive_start_addr && byte_counter == 8'h00) begin | |
| start_addr[15:8] <= uart_rx_data; | |
| end | |
| else if (receive_start_addr && byte_counter == 8'h01) begin | |
| start_addr[7:0] <= uart_rx_data; | |
| end | |
| else if (receive_quantity && byte_counter == 8'h00) begin | |
| quantity[15:8] <= uart_rx_data; | |
| end | |
| else if (receive_quantity && byte_counter == 8'h01) begin | |
| quantity[7:0] <= uart_rx_data; | |
| end | |
| else if (receive_byte_count) begin | |
| byte_count <= uart_rx_data; | |
| end | |
| else if (receive_data && byte_counter[0] == 1'b0) begin | |
| fifo_din_reg[15:8] <= uart_rx_data; | |
| end | |
| else if (receive_data && byte_counter[0] == 1'b1) begin | |
| fifo_din_reg[7:0] <= uart_rx_data; | |
| end | |
| else if (receive_crc && byte_counter == 8'h00) begin | |
| crc16[7:0] <= uart_rx_data; | |
| end | |
| else if (receive_crc && byte_counter == 8'h01) begin | |
| crc16[15:8] <= uart_rx_data; | |
| end | |
| end | |
| else if (uart_rx_ready && uart_rx_rden) begin // Change state depending on request frame | |
| if (receive_func) begin | |
| receive_func <= 1'b0; | |
| if (errcode == 8'h00) begin | |
| if (func == 8'h03 || func == 8'h10) begin // If function code is not valid, set error code 0x01 | |
| receive_start_addr <= 1'b1; | |
| end | |
| else begin | |
| errcode <= 8'h01; | |
| end | |
| end | |
| end | |
| else if (receive_start_addr && byte_counter == 8'h00) begin | |
| byte_counter <= 8'h01; | |
| end | |
| else if (receive_start_addr && byte_counter == 8'h01) begin | |
| byte_counter <= 8'h00; | |
| receive_start_addr <= 1'b0; | |
| receive_quantity <= 1'b1; | |
| end | |
| else if (receive_quantity && byte_counter == 8'h00) begin | |
| byte_counter <= 8'h01; | |
| end | |
| else if (receive_quantity && byte_counter == 8'h01) begin | |
| byte_counter <= 8'h00; | |
| receive_quantity <= 1'b0; | |
| if (errcode == 8'h00) begin | |
| if (quantity == 16'h0000 || quantity > 16'h007d || (func == 8'h10 && quantity > 16'h007b)) begin // If quantity is invalid, set error code 0x03 | |
| errcode <= 8'h03; | |
| end | |
| else if (start_addr[15:8] != 8'h00 || (func == 8'h03 && start_addr + quantity > 16'h0100) || (func == 8'h10 && start_addr + quantity > 16'h0080)) begin // If address is invalid, set error code 0x02 | |
| errcode <= 8'h02; | |
| end | |
| else if (func == 8'h03) begin // If function is read holding registers, receive crc | |
| receive_crc <= 1'b1; | |
| end | |
| else if (func == 8'h10) begin // If function is write multiple registers, receive byte count | |
| receive_byte_count <= 1'b1; | |
| end | |
| end | |
| end | |
| else if (receive_byte_count) begin | |
| receive_byte_count <= 1'b0; | |
| receive_data <= 1'b1; | |
| errcode <= (byte_count[7:1] != quantity[6:0] && errcode == 8'h00) ? 8'h04 : errcode; | |
| end | |
| else if (receive_data && byte_counter[0] == 1'b0) begin | |
| byte_counter <= byte_counter + 1; | |
| end | |
| else if (receive_data && byte_counter[0] == 1'b1) begin | |
| byte_counter <= byte_counter + 1; | |
| fifo_we <= 1'b1; | |
| if (byte_counter == byte_count - 1) begin | |
| byte_counter <= 8'h00; | |
| receive_data <= 1'b0; | |
| receive_crc <= 1'b1; | |
| end | |
| end | |
| else if (receive_crc && byte_counter == 8'h00) begin | |
| byte_counter <= 8'h01; | |
| end | |
| else if (receive_crc && byte_counter == 8'h01) begin | |
| byte_counter <= 8'h00; | |
| receive_crc <= 1'b0; | |
| end | |
| end | |
| else begin | |
| if(timeout_counter < timeout_count) begin // Wait for 4 bytes to finish receiving request | |
| timeout_counter <= timeout_counter + 1; | |
| end | |
| else begin | |
| receive_func <= 1'b0; | |
| receive_start_addr <= 1'b0; | |
| receive_quantity <= 1'b0; | |
| receive_data <= 1'b0; | |
| receive_crc <= 1'b0; | |
| byte_counter <= 8'h00; | |
| timeout_counter <= 16'h0000; | |
| state <= CHECK_ERRORS; | |
| end | |
| end | |
| end | |
| CHECK_ERRORS : begin | |
| crc_soft_rst <= 1'b1; | |
| crc_data <= 8'h00; | |
| crc_enable <= 1'b1; | |
| byte_counter <= 8'h00; | |
| uart_rx_data_q0 <= 8'h00; | |
| uart_rx_data_q1 <= 8'h00; | |
| timeout_counter <= 16'h0000; | |
| if ((errcode == 8'h00 && crc_crc16 != crc16) || (errcode != 8'h00 && crc_crc16 != {uart_rx_data_q0, uart_rx_data_q1}) || errcode == 8'hff) begin | |
| fifo_soft_rst <= 1'b1; | |
| fifo_din_reg <= 16'h0000; | |
| crc_enable <= 1'b0; | |
| id <= 8'h00; | |
| func <= 8'h00; | |
| start_addr <= 16'h0000; | |
| quantity <= 16'h0000; | |
| crc16 <= 16'h0000; | |
| errcode <= 8'h00; | |
| uart_rx_data_q0 <= 8'h00; | |
| uart_rx_data_q1 <= 8'h00; | |
| state <= IDLE; | |
| end | |
| else if (errcode != 8'h00) begin // If there is an error, just send error response | |
| send_id <= 1'b1; | |
| state <= SEND_RESPONSE; | |
| end | |
| else if (func == 8'h03) begin // If the function is read holding registers, read memory | |
| o_mem_addr <= start_addr[7:0]; | |
| o_mem_rden <= 1'b1; | |
| state <= MEM_READ; | |
| end | |
| else if (func == 8'h10) begin // If the function is write multiple registers, write memory | |
| fifo_re <= 1'b1; | |
| o_mem_addr <= start_addr[7:0]; | |
| byte_counter <= byte_counter + 2; | |
| state <= MEM_WRITE; | |
| end | |
| else begin | |
| o_mem_addr <= 8'h00; | |
| o_mem_wren <= 1'b0; | |
| o_mem_rden <= 1'b0; | |
| uart_tx_data <= 8'h00; | |
| fifo_soft_rst <= 1'b1; | |
| fifo_din_reg <= 16'h0000; | |
| fifo_re <= 1'b0; | |
| fifo_we <= 1'b0; | |
| id <= 8'h00; | |
| func <= 8'h00; | |
| start_addr <= 16'h0000; | |
| quantity <= 16'h0000; | |
| crc16 <= 16'h0000; | |
| errcode <= 8'h00; | |
| receive_func <= 1'b0; | |
| receive_start_addr <= 1'b0; | |
| receive_quantity <= 1'b0; | |
| receive_data <= 1'b0; | |
| receive_crc <= 1'b0; | |
| send_id <= 1'b0; | |
| send_func <= 1'b0; | |
| send_start_addr <= 1'b0; | |
| send_quantity <= 1'b0; | |
| send_byte_count <= 1'b0; | |
| send_data <= 1'b0; | |
| send_crc <= 1'b0; | |
| send_errcode <= 1'b0; | |
| state <= IDLE; | |
| end | |
| end | |
| MEM_WRITE : begin | |
| if (i_mem_wrready) begin | |
| if (byte_counter[7:1] == quantity[6:0]) begin | |
| if (fifo_re) begin | |
| o_mem_addr <= (o_mem_wren) ? o_mem_addr + 1 : o_mem_addr; | |
| o_mem_wren <= 1'b1; | |
| end | |
| else begin | |
| o_mem_addr <= 8'h00; | |
| o_mem_wren <= 1'b0; | |
| byte_counter <= 8'h00; | |
| send_id <= 1'b1; | |
| state <= SEND_RESPONSE; | |
| end | |
| end | |
| else begin | |
| fifo_re <= 1'b1; | |
| o_mem_wren <= 1'b1; | |
| o_mem_addr <= (o_mem_wren) ? o_mem_addr + 1 : o_mem_addr; // Increase address after the first read | |
| byte_counter <= byte_counter + 2; | |
| end | |
| end | |
| end | |
| MEM_READ : begin | |
| if (byte_counter[7:1] == quantity[6:0]) begin | |
| o_mem_addr <= 8'h00; | |
| o_mem_rden <= 1'b0; | |
| byte_counter <= 8'h00; | |
| send_id <= 1'b1; | |
| state <= SEND_RESPONSE; | |
| end | |
| else begin | |
| fifo_we <= 1'b1; | |
| o_mem_addr <= o_mem_addr + 1; | |
| o_mem_rden <= 1'b1; | |
| byte_counter <= byte_counter + 2; | |
| end | |
| end | |
| SEND_RESPONSE : begin | |
| if (uart_tx_ready && ~uart_tx_wren) begin // Send data. The data will be determined by send state flags | |
| if (send_id) begin | |
| uart_tx_wren <= 1'b1; | |
| uart_tx_data <= device_id; | |
| send_id <= 1'b0; | |
| send_func <= 1'b1; | |
| end | |
| else if (send_func) begin | |
| uart_tx_wren <= 1'b1; | |
| if (errcode != 8'h00) begin | |
| uart_tx_data <= func | 8'h80; // 80 + func will be sent if there is an error | |
| send_errcode <= 1'b1; | |
| end | |
| else if (func == 8'h03) begin | |
| uart_tx_data <= func; | |
| send_byte_count <= 1'b1; | |
| end | |
| else if (func == 8'h10) begin | |
| uart_tx_data <= func; | |
| send_start_addr <= 1'b1; | |
| end | |
| send_func <= 1'b0; | |
| end | |
| else if (send_start_addr && byte_counter == 8'h00) begin | |
| uart_tx_wren <= 1'b1; | |
| uart_tx_data <= start_addr[15:8]; | |
| byte_counter <= 8'h01; | |
| end | |
| else if (send_start_addr && byte_counter == 8'h01) begin | |
| uart_tx_wren <= 1'b1; | |
| uart_tx_data <= start_addr[7:0]; | |
| byte_counter <= 8'h00; | |
| send_start_addr <= 1'b0; | |
| send_quantity <= 1'b1; | |
| end | |
| else if (send_quantity && byte_counter == 8'h00) begin | |
| uart_tx_wren <= 1'b1; | |
| uart_tx_data <= quantity[15:8]; | |
| byte_counter <= 8'h01; | |
| end | |
| else if (send_quantity && byte_counter == 8'h01) begin | |
| uart_tx_wren <= 1'b1; | |
| uart_tx_data <= quantity[7:0]; | |
| byte_counter <= 8'h00; | |
| send_quantity <= 1'b0; | |
| send_crc <= 1'b1; | |
| end | |
| else if (send_byte_count) begin | |
| uart_tx_wren <= 1'b1; | |
| uart_tx_data <= {quantity[6:0], 1'b0}; | |
| send_byte_count <= 1'b0; | |
| send_data <= 1'b1; | |
| end | |
| else if (send_data && byte_counter[0] == 1'b0) begin | |
| if (~fifo_re && ~fifo_data_ready) begin | |
| fifo_re <= 1'b1; | |
| end | |
| else if (fifo_re && ~fifo_data_ready) begin | |
| fifo_data_ready <= 1'b1; | |
| end | |
| else begin | |
| fifo_data_ready <= 1'b0; | |
| uart_tx_wren <= 1'b1; | |
| uart_tx_data <= fifo_dout[15:8]; | |
| byte_counter <= byte_counter + 1; | |
| end | |
| end | |
| else if (send_data && byte_counter[0] == 1'b1) begin | |
| uart_tx_wren <= 1'b1; | |
| uart_tx_data <= fifo_dout[7:0]; | |
| if (byte_counter + 1 == {quantity[6:0], 1'b0}) begin | |
| byte_counter <= 8'h00; | |
| send_data <= 1'b0; | |
| send_crc <= 1'b1; | |
| end | |
| else begin | |
| byte_counter <= byte_counter + 1; | |
| end | |
| end | |
| else if (send_errcode) begin | |
| uart_tx_wren <= 1'b1; | |
| uart_tx_data <= errcode; | |
| send_errcode <= 1'b0; | |
| send_crc <= 1'b1; | |
| end | |
| else if (send_crc && byte_counter == 8'h00) begin | |
| uart_tx_wren <= 1'b1; | |
| uart_tx_data <= crc_crc16[7:0]; | |
| byte_counter <= 8'h01; | |
| end | |
| else if (send_crc && byte_counter == 8'h01) begin | |
| uart_tx_wren <= 1'b1; | |
| uart_tx_data <= crc_crc16[15:8]; | |
| byte_counter <= 8'h00; | |
| send_crc <= 1'b0; | |
| end | |
| end | |
| else if (uart_tx_ready && uart_tx_wren) begin | |
| if (~send_id && ~send_func && ~send_start_addr && ~send_quantity && | |
| ~send_byte_count && ~send_data && ~send_errcode && ~send_crc) begin // If no flags are set, finish sending response | |
| uart_tx_data <= 8'h00; | |
| crc_soft_rst <= 1'b1; | |
| crc_data <= 8'h00; | |
| fifo_soft_rst <= 1'b1; | |
| fifo_data_ready <= 1'b0; | |
| id <= 8'h00; | |
| func <= 8'h00; | |
| start_addr <= 16'h0000; | |
| quantity <= 16'h0000; | |
| crc16 <= 16'h0000; | |
| errcode <= 8'h00; | |
| state <= IDLE; | |
| end | |
| else begin// Compute CRC16 | |
| if (send_crc) begin | |
| crc_enable <= 1'b0; | |
| end | |
| crc_start <= (crc_enable) ? 1'b1 : 1'b0; | |
| crc_data <= uart_tx_data; | |
| end | |
| end | |
| end | |
| default : state <= IDLE; | |
| endcase | |
| end | |
| end | |
| end | |
| endmodule |