In the last blog entry I mapped memory block to VGA screen. But it was boring to see how memory is being filled with ones by very simple process. So last couple of days I was working on connecting my FPGA to computer with UART.
I use cheap Chinese USB-to-UART converter. The same that was used in my first blog port.
This time I decided I don't want to implement UART communication by myself. I found a project on opencores.org that was ready to use. All I had to do was to connect UART controller to a memory block.
In the future I plan to use UART connection not only to read and write memory, but also to control other modules (processor, memory, etc.) on FPGA chip. This can be done by assigning address space to these modules and handling read/write operations in a module specific manner.
As for now I am only connecting 1kB block of RAM, but I will interface trough a routing module anyway. Let's see input and output signals of this module:
module uart_sram_bridge ( input wire clk50_dup, input wire [15:0] uart_address, // address bus to register file output wire [7:0] uart_read_data, // write data to register file input wire [7:0] uart_write_data, // write data to register file input wire uart_write, // write control to register file input wire uart_read, // read control to register file input wire uart_req, // bus access request signal output wire uart_gnt, // bus access grant signal output reg [9:0] sram_address, input wire [7:0] sram_read_data, output reg [7:0] sram_write_data, output wire sram_write_enable );
We have a clock signal. Not used now, but may be useful later.
We have a section of signals that are connected to UART interface. UART
module handles incoming read/write commands and asserts uart_read
,
uart_write
signals, passing address and data on uart_read_data
and uart_write_data
. UART module will set uart_read
or uart_write
only when uart_gnt
is high.
Than we a have section of signals that go to/from RAM.
My first attempt of connecting UART to RAM is very simple:
assign sram_address = uart_address[9:0]; assign sram_write_data = uart_write_data; assign uart_read_data = sram_read_data; assign sram_write_enable = uart_write; assign uart_gnt = 1;
After connecting signals as it is described in the previous section,
I found that it's not working. After publishing this post
and testing it once again, I discovered that it is working.
I really don't know what was the problem. Anyway, you may
read my attempt to make it work below. It may be applicable in other
situations.
The usual problem is that RAM memory expects address and data signals to be stable some time before positive clock signal edge. This can be done by using memory clock signal that is delayed by 180°. But it didn't help. I could plainly see (that part was working) that I can't write to the memory. I don't know why.
I suspected that some signals are delayed or there is not enough time for the memory, etc. Since UART is much slower than internal clock, I decided to hold signals for configured number of clock cycles.
This can be done with a simple latch. When read or write request comes
from UART I latch data and address signals for a couple of cycles. I also
set uart_gnt
signal low to block requests when memory is busy.
Delay is a parameter of a module:
module uart_sram_bridge #( parameter LATCH_DELAY = 1, parameter LATCH_DELAY_W = 1 ) ...
We start counting from 0 up to LATCH_DELAY when write request is granted.
wire write_req_granted; assign write_req_granted = uart_write & uart_gnt; // count from 0 to LATCH_DELAY starting on write_req_granted signal reg [LATCH_DELAY_W-1:0] wr_latch_delay; initial wr_latch_delay = LATCH_DELAY; always @(posedge clk50_dup) begin if (write_req_granted) wr_latch_delay <= 0; else if (wr_latch_delay != LATCH_DELAY) wr_latch_delay <= wr_latch_delay + 1'b1; end
We assert sram_write_enable
for LATCH_DELAY cycles and set uart_gnt
low
when write is in progress.
assign sram_write_enable = wr_latch_delay < LATCH_DELAY; // set sram_write_enable for LATCH_DELAY clock cycles assign uart_gnt = !sram_write_enable; // block bus while we write
Order of assign commands doesn't matter in Verilog. Code above could be rewritten as:
assign write_req_granted = uart_write & !(wr_latch_delay < LATCH_DELAY);
This ensures that after wr_latch_delay <= 0
is executed at positive edge
of clock signal write_req_granted
will be low, so counting will continue
until LATCH_DELAY
is reached.
Finally, latches:
always @* begin if (uart_req) // read or write - anyway - address is necessary begin sram_address <= uart_address[9:0]; end if (write_req_granted) begin // latch data and address on write_req_granted signal sram_write_data <= uart_write_data; end end
As you can see:
else
parts of if
statements that do assignment. So once
condition is false, value will stay as it was last set (it is latched).This version works perfectly fine. Xilinx FPGA synthesis software doesn't like it. It warns about it:
Xst:737 - Found 1-bit latch for signal <sram_address<9>>. Latches may be generated from incomplete case or if statements. We do not recommend the use of latches in FPGA/CPLD designs, as they may lead to timing problems.
I don't know how much of a truth is in this warning. I've checked that version with flip-flop also works.
Flip-flop is basically a pair of latches, so it seems that using flip-flops is more expensive. But that's not the case. Spartan FPGA is optimized for usage of flip-flops, so when I use a latch, one half of a flip-flop is wasted and there is no gain is resources usage.
What to do to convert latches to flip-flops? It's enough to say that transitions should happen only on a clock edge:
always @(posedge clk50_dup) // also works with @* (latches instead of flip-flops) ...
The plan was to connect UART in an hour. It took much more time. But I've learnt something about latches vs flip-flops. And I hope this article is more interesting because of that too.
As usually, you can find latest version of my project on GitHub.