summaryrefslogtreecommitdiffstats
path: root/serial.v
blob: 1bf6beb9e6f3f0f871c863bcec44052cc0908cd0 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
// -----------------------------------------------------------------------
//
//   Copyright 2004-2010 H. Peter Anvin - All Rights Reserved
//
//   This program is free software; you can redistribute it and/or modify
//   it under the terms of the GNU General Public License as published by
//   the Free Software Foundation, Inc., 53 Temple Place Ste 330,
//   Bostom MA 02111-1307, USA; either version 2 of the License, or
//   (at your option) any later version; incorporated herein by reference.
//
// -----------------------------------------------------------------------

//
// serial.v
//
// Simple fixed-speed serial port
//
// The following registers are implemented:
//
// R 0 - data in
// W 0 - data out
// R 1 - status:
//       bit 0 - data available in input FIFO
//       bit 1 - space available in output FIFO
//       bit 2 - input FIFO full
//       bit 3 - output FIFO empty
// W 1 - control:
//       bit 0 - clean input FIFO
//       bit 1 - clear output FIFO
//
// On the 68000 side these are mapped to the low half of two separate
// bytes, so a[0] is actually a[1].
//

module serial (
	       input	    rst_n,	// Global reset
	       input        clk,	// Clock (25 MHz)
	       
	       output       tty_txd,	// Serial port data out
	       input        tty_rxd,	// Serial port data in
	       
	       input        msel,	// Unit (card/chip) select, AS#
	       input        cpu_a,	// Address bit
	       input [7:0]  cpu_do,	// CPU data out (cpu->card)
	       output [7:0] cpu_di,	// CPU data in  (card->cpu)
	       input        cpu_r_wn	// R/W#
	       );

   // CPU strobes
   wire         cpu_rd  = msel & cpu_r_wn;
   wire 	cpu_wr  = msel & ~cpu_r_wn;
   wire 	cpu_rdd = cpu_rd & ~cpu_a;
   wire 	cpu_wrd = cpu_wr & ~cpu_a;
   
   // Data out
   reg [9:0] 	 tx_data_sr;	// Serial port shift register
   reg 		 tx_data_out;	// Serial port data out
   assign        tty_txd = tx_data_out;
   
   // Baud rate divider
   parameter	 baud_rate	= 115200;
   parameter	 baud_div	= (25000000/baud_rate)-1;
   reg [7:0] 	 baud_rate_ctr;
   // Time to advance the serial register
   wire 	 advance_bit = ~|baud_rate_ctr;

   // BUSY counter
   reg [3:0] 	 tx_busy_ctr;
   wire 	 tx_busy = |tx_busy_ctr;

   // Status generation
   wire 	 rx_empty;
   wire [7:0] 	 status = { 6'b0, tx_busy, ~rx_empty };
   wire [7:0] 	 rx_cpu_data;
   assign 	 cpu_di = cpu_rd ? (cpu_a ? status : rx_cpu_data) : 8'hFF;

   // Control register
   wire 	 rx_clear = cpu_wr & cpu_a & cpu_do[0];
   
   // Edge detect
   reg 		 cpu_wrd_q;
   reg 		 cpu_rdd_q;
      
   always @(negedge rst_n or posedge clk)
     begin
	if ( ~rst_n )
	  begin
	     baud_rate_ctr <= 8'h0;
	     tx_data_sr    <= ~9'b0;
	     tx_data_out   <= 1'b1;
	     tx_busy_ctr   <= 4'd0;
	     cpu_wrd_q     <= 1'b1;
	     cpu_rdd_q     <= 1'b1;
	  end
	else
	  begin
	     if ( cpu_wrd & ~cpu_wrd_q )
	       tx_data_sr <= { 1'b1, cpu_do, 1'b0 };
	     else if ( advance_bit )
	       tx_data_sr <= { 1'b1, tx_data_sr[8:1] };

	     if ( advance_bit )
	       tx_data_out <= tx_data_sr[0];

	     if ( advance_bit )
	       baud_rate_ctr <= baud_div;
	     else
	       baud_rate_ctr <= baud_rate_ctr - 1'b1;

	     if ( cpu_wrd & ~cpu_wrd_q )
	       tx_busy_ctr <= 4'd10;
	     else if ( advance_bit & tx_busy )
	       tx_busy_ctr <= tx_busy_ctr - 1'b1;

	     cpu_rdd_q <= cpu_rdd;
	     cpu_wrd_q <= cpu_wrd;
	  end // else: !if( ~rst_n )
     end // always @ (negedge rst_n or posedge clk)

   // Deglitch for the input
   wire		 rx_deglitched;
   
   debounce #(.width(1), .count(4)) rx_deglitch
     (
      .clk	( clk ),
      .in	( tty_rxd ),
      .out	( rx_deglitched ),
      .strobe	( )
      );

   // Receive logic
   reg [3:0] rx_bit_ctr;
   reg [8:0] rx_data;
   reg [7:0] rx_wait_ctr;
   reg 	     rx_write_stb;

   always @(negedge rst_n or posedge clk)
     if (~rst_n)
       begin
	  rx_bit_ctr   <= 4'd0;
	  rx_data      <= 9'hxxx;
	  rx_wait_ctr  <= 9'hxxx;
	  rx_write_stb <= 1'b0;
       end
     else
       begin
	  rx_write_stb <= 1'b0;
	  if (~|rx_bit_ctr)
	    begin
	       if (~rx_deglitched)
		 begin
		    rx_bit_ctr  <= 4'd1;
		    // Sample near the center point; half a cycle delay
		    rx_wait_ctr <= {1'b0, baud_div[7:1]};
		 end
	    end
	  else
	    begin
	       if (|rx_wait_ctr)
		 rx_wait_ctr <= rx_wait_ctr - 1;
	       else
		 begin
		    rx_wait_ctr <= baud_div;
		    rx_data     <= { rx_deglitched, rx_data[8:1] };
		    if (rx_bit_ctr[3] & rx_bit_ctr[1])
		      begin
			 // rx_data[0] and rx_deglitched are the start and
			 // stop bits, respectively.  Verify that both
			 // were properly detected.
			 rx_write_stb <= ~rx_data[0] & rx_deglitched;
			 rx_bit_ctr   <= 4'd0;
		      end
		    else
		      rx_bit_ctr <= rx_bit_ctr + 1;
		 end // else: !if(&rx_wait_ctr)
	    end // else: !if(~|rx_ctr)
       end // else: !if(~rst_n)

   //
   // Serial receive FIFO
   //
   bytefifo serrxfifo
     (
      .clock		( clk ),
      .data		( rx_data[7:0] ),
      .wrreq		( rx_write_stb ),
      .sclr		( ~rst_n | rx_clear ),
      .almost_full	( ),
      .empty		( rx_empty ),
      .full		( ),
      .rdreq		( cpu_rdd & ~cpu_rdd_q ),
      .q		( rx_cpu_data ),
      .usedw		( )
      );
   
endmodule // serial