Skip to content

Commit 8274c6a

Browse files
committed
New uart, runs at 921600. Fixes jamesbowman#45; helps jamesbowman#15,jamesbowman#39
This contains an alternate buart() module for asynchronous RS232. - will resynchronise itself at every start bit transition - rejects bad characters - does not clobber last valid data - keeps the most recently received character, 'valid' flag always clears on read. - works reliably up to 921600 baud on both ice40hx8k breakout and iceStick - works with other 'logic level' rs232 devices offboard, or via max232 chips to true rs232 ports. - easy to instantiate multiple ports with different baud rates This also adds a "make pcon" option to remember the settings for connecting with picocom, which is useful for testing character-by-character operation, as well as manual control over the reset line. It sometimes works if the uart connection is marginal, and shell.py gets stuck on connect.
1 parent 60872cd commit 8274c6a

File tree

7 files changed

+139
-10
lines changed

7 files changed

+139
-10
lines changed

j1a/Makefile

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,4 +55,10 @@ mackextload:
5555

5656
endif
5757

58-
.PHONY: connect clean bootstrap mackextload mackextunload macconnect
58+
pcon:
59+
$(info Use C-a C-t to toggle reset line as necessary)
60+
$(info Use C-a C-x to exit. No shell.py features are available)
61+
picocom -b 921600 /dev/ttyUSB1 --imap lfcrlf,crcrlf --omap delbs,crlf --send-cmd "ascii-xfr -s -l 30 -n"
62+
63+
64+
.PHONY: connect sim_connect j4a_sim_connect clean bootstrap mackextload mackextunload macconnect linmodload pcon

j1a/icestorm/Makefile

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
VERILOGS = j1a.v uart.v ../verilog/j1.v ../verilog/stack2.v
1+
VERILOGS = j1a.v ../verilog/async_in_filter.v uart3.v ../verilog/j1.v ../verilog/stack2.v
22

3-
VERILOGS8k = j1a8k.v uart.v ../verilog/j1.v ../verilog/stack2.v
3+
VERILOGS8k = j1a8k.v uart3.v ../verilog/async_in_filter.v ../verilog/j1.v ../verilog/stack2.v
44

5-
VERILOGS8k4 = j4a.v uart.v ../verilog/j1.v ../verilog/stack2.v ../verilog/j4.v ../verilog/stack2pipe4.v ../verilog/greycount.v
5+
VERILOGS8k4 = j4a.v uart3.v ../verilog/*.v
66

77
SUBDIRS = ..
88

j1a/icestorm/j1a.v

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -266,8 +266,8 @@ module top(input pclk, output D1, output D2, output D3, output D4, output D5,
266266
wire uart0_wr = io_wr_ & io_addr_[12];
267267
wire uart0_rd = io_rd_ & io_addr_[12];
268268
wire uart_RXD;
269-
inpin _rcxd(.clk(clk), .pin(RXD), .rd(uart_RXD));
270-
buart _uart0 (
269+
async_in_filter _rcxd(.clk(clk), .pin(RXD), .rd(uart_RXD));
270+
buart #(.BAUD(921600)) _uart0 (
271271
.clk(clk),
272272
.resetq(1'b1),
273273
.rx(uart_RXD),

j1a/icestorm/j1a8k.v

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -309,7 +309,7 @@ module top(input pclk,
309309
wire uart0_rd = io_rd_ & io_addr_[12];
310310
wire uart_RXD;
311311
inpin _rcxd(.clk(clk), .pin(RXD), .rd(uart_RXD));
312-
buart _uart0 (
312+
buart #(.BAUD(921600)) _uart0 (
313313
.clk(clk),
314314
.resetq(1'b1),
315315
.rx(uart_RXD),

j1a/icestorm/j4a.v

Lines changed: 56 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -306,7 +306,61 @@ module top(input pclk,
306306
.rd(pmod_in),
307307
.dir(pmod_dir));
308308

309-
// ###### HDR1 ##########################################
309+
always @(posedge clk) if (io_wr_ & io_addr_[1]) pmod_dir <= masked_pmod_dir;
310+
// This allows the direction pins to be set with an iomask'd write - very handy when sharing the port between multiple threads.
311+
312+
// ###### AB Quadrature Decoder ##############################
313+
wire encA,encB;
314+
async_in_filter _encpinA(.clk(clk), .pin(encA_in), .rd(encA));
315+
async_in_filter _encpinB(.clk(clk), .pin(encB_in), .rd(encB));
316+
// encoder is asynchronous, so this helps with metastability:
317+
reg [31:0] position;
318+
reg [1:0] encs, encs_; always @(posedge clk) encs <= encs_;
319+
reg step,dir;
320+
always @* begin
321+
casez ({encs, encA, encB})
322+
4'b00_10: {encs_,step,dir} = 4'b10_11;
323+
4'b00_01: {encs_,step,dir} = 4'b01_10;
324+
4'b01_11: {encs_,step,dir} = 4'b11_10;
325+
4'b01_00: {encs_,step,dir} = 4'b00_11;
326+
4'b11_10: {encs_,step,dir} = 4'b10_10;
327+
4'b11_01: {encs_,step,dir} = 4'b01_11;
328+
4'b10_00: {encs_,step,dir} = 4'b00_10;
329+
4'b10_11: {encs_,step,dir} = 4'b11_11;
330+
default: {encs_,step,dir} = {encs,1'b0,dir};
331+
endcase
332+
end
333+
wire samplepos = io_wr_ & io_addr_[13] & dout_[8];
334+
wire sethome = io_wr_ & io_addr_[13] & dout_[9];
335+
wire readpos = io_rd_ & io_addr_[6];
336+
337+
always @(posedge clk) position <= sethome ? 32'b0 : (step ? (position - ( dir ? -32'd1 : 32'd1)) : position);
338+
reg [31:0] pos_sample; always @(posedge clk) pos_sample <= samplepos ? position : (readpos ? {16'b0,pos_sample[31:16]} : pos_sample);
339+
wire [15:0] pos_word = pos_sample[15:0];
340+
341+
// ###### Secondary 9600 8N1 UART ##############################
342+
343+
344+
wire uart1_valid, uart1_busy;
345+
wire [7:0] uart1_data;
346+
wire uart1_wr = io_wr_ & io_addr_[5];
347+
wire uart1_rd = io_rd_ & io_addr_[5];
348+
wire uart_RXD2;
349+
async_in_filter _rcxd1(.clk(clk), .pin(RXD2), .rd(uart_RXD2));
350+
// .CLOCK_DIVIDE takes CLKfreq / (4*baud)
351+
buart #(.BAUD(9600)) _uart1 (
352+
.clk(clk),
353+
.resetq(1'b1),
354+
.rx(uart_RXD2),
355+
.tx(TXD2),
356+
.rd(uart1_rd),
357+
.wr(uart1_wr),
358+
.valid(uart1_valid),
359+
.busy(uart1_busy),
360+
.tx_data(dout_[7:0]),
361+
.rx_data(uart1_data),
362+
.error(U2ERR));
363+
>>>>>>> e367b69... New uart, runs at 921600. Fixes #45; helps #15,#39
310364

311365
reg [7:0] hdr1_dir; // 1:output, 0:input
312366
wire [7:0] hdr1_in;
@@ -338,7 +392,7 @@ module top(input pclk,
338392
wire uart0_rd = io_rd_ & io_addr_[12];
339393
wire uart_RXD;
340394
async_in_filter _rcxd(.clk(clk), .pin(RXD), .rd(uart_RXD));
341-
buart #(.CLOCK_DIVIDE(313)) _uart0 (
395+
buart #(.BAUD(921600)) _uart0 (
342396
.clk(clk),
343397
.resetq(1'b1),
344398
.rx(uart_RXD),

j1a/icestorm/uart3.v

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
`default_nettype none
2+
3+
module buart(
4+
input clk, // The master clock for this module
5+
input resetq, // Synchronous reset, active low
6+
input rx, // Incoming serial line
7+
output tx, // Outgoing serial line
8+
input rd, // read strobe -- used only to clear valid flag.
9+
input wr, // write strobe
10+
output reg valid, // Indicates a new byte is available. clears on read.
11+
output reg busy, // Low when transmit line is idle.
12+
input [7:0] tx_data, // Byte to transmit
13+
output reg [7:0] rx_data, // *Most recent* byte received -- whether or not the last was collected.
14+
output reg error // reception error
15+
);
16+
// you can override these on a per-port basis, looks like:
17+
// buart #(.BAUD(115200)) _youruart (.clk(clk)...etc);
18+
// or
19+
// buart #(.CLOCK_DIVIDE(312)) _uart1 (...
20+
// The latter might be better for designs with non-48MHz clocks.
21+
parameter BAUD = 9600;
22+
parameter CLKFREQ = 48000000; // frequency of incoming signal 'clk'
23+
parameter CLOCK_DIVIDE = (CLKFREQ / (BAUD * 4)); // clock rate (48Mhz) / (baud rate (460800) * 4)
24+
// will probably want to support at least down to 9600 baud, which will require a CLOCK_DIVIDE == 1250
25+
26+
localparam CDSIZE = $clog2(CLOCK_DIVIDE)+1; // one more to accomodate assumed signed arithmatic
27+
reg [5:0] bytephase;
28+
reg [CDSIZE-1:0] rxclkcounter;
29+
wire rxqtick = rxclkcounter == CLOCK_DIVIDE; // strobes high one clk every 1/4 bit time
30+
wire rxrst = rx & (~|bytephase); // rx goes low with the beginning of the start bit. synchronous to system clk, not sample clk.
31+
always @(posedge clk) rxclkcounter <= rxrst | rxqtick ? 1 : rxclkcounter + 1; // initially held in reset
32+
// very important: idle rx line holds rxrst asserted,
33+
// this goes on *until* the start edge is found.
34+
// thus synchronising further sampling to that edge, rather than remaining in phase with however it was reset.
35+
36+
wire rxstop = bytephase == 6'd40; // 11th sample 'tick' would have been at 42.
37+
wire nonstarter;
38+
always @(posedge clk) bytephase <= rxstop|nonstarter ? 0 : rxqtick ? bytephase + 1 : bytephase;
39+
wire sample = (bytephase[1:0] == 2'b10) & rxqtick; // one clk for each of ten bits
40+
// note sample is false while rxrst is true.
41+
assign nonstarter = (bytephase == 6'd2) & rx; // start bit should still be low when sample strobes first.
42+
// if it isn't, then it will go back to a rxrst state.
43+
44+
// after this point, we have a sample strobe, a rxstop strobe
45+
reg [9:0] capture; always @(posedge clk) capture <= sample ? {rx, capture[9:1]} : capture;
46+
// note bits are sent least-significant first.
47+
wire startbit = capture[0]; // valid when rxstop strobes, and until rxrst releases for the next byte.
48+
wire stopbit = capture[9];
49+
wire good = stopbit&~startbit; // valid when rxstop is asserted. stop bit should be 1, start bit should have been zero.
50+
always @(posedge clk)
51+
begin
52+
valid <= rd ? 1'b0 : rxstop & good ? 1'b1 : valid;
53+
rx_data <= rxstop & good ? capture[8:1] : rx_data;
54+
error <= nonstarter ? 1'b1 : rxstop ? ~good : error ;
55+
end
56+
// tx parts
57+
reg [CDSIZE+1:0] txclkcounter; // note, two extra bits to accomodate a limit 4x as large.
58+
wire txtick = txclkcounter == 4*CLOCK_DIVIDE; // ticks for a clk once every bit, not every quarter bit.
59+
always @(posedge clk) txclkcounter <= txtick ? 1 : txclkcounter + 1;
60+
// note txclkcounter never needs to be reset out-of-phase with itself.
61+
reg [3:0] sentbits;
62+
wire done = sentbits == 4'd10; // eventually stays 'done'. Reset to zero again when wr strobes.
63+
always @(posedge clk) sentbits <= txtick & ~done ? sentbits + 1 : wr ? 4'd0 : sentbits ;
64+
reg [9:0] sender;
65+
// wr strobe might come any clk, not synchronous to txtick. No real need to force txtick to be synchronous to it either.
66+
always @(posedge clk) sender <= wr ? {tx_data, 1'b0, 1'b1} : txtick ? {1'b1, sender[9:1]} : sender;
67+
assign tx = sender[0]; // wr loads this 1, because tranmission doesn't start until the next txtick, whenever it arrives.
68+
assign busy = ~done;
69+
endmodule

j1a/shell.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ def open_ser(self, port, speed):
1818
except:
1919
print("This tool needs PySerial, but it was not found")
2020
sys.exit(1)
21-
self.ser = serial.Serial(port, 4 * 115200, timeout=None, rtscts=0)
21+
self.ser = serial.Serial(port, 921600, timeout=None, rtscts=0)
2222

2323
def reset(self, fullreset = True):
2424
ser = self.ser

0 commit comments

Comments
 (0)