aboutsummaryrefslogtreecommitdiffstats
path: root/simple_gemac/simple_gemac_tx.v
blob: 690fd5c374482c858dbeb6e49bab9a6fbdee66b6 (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
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
module simple_gemac_tx
  (input clk125, input reset,
   output GMII_GTX_CLK, output reg GMII_TX_EN, output reg GMII_TX_ER, output reg [7:0] GMII_TXD,
   output tx_clk, input [7:0] tx_data, input tx_valid, input tx_error, output tx_ack,
   input [7:0] ifg, input [47:0] mac_addr,
   input pause_req, input [15:0] pause_time,
   input pause_apply, output reg paused
   );

   reg tx_en_pre, tx_er_pre;
   reg [7:0] txd_pre;
   
   assign GMII_GTX_CLK 	= clk125;
   assign tx_clk 	= clk125;
   
   reg [7:0] tx_state;
   reg [7:0] ifg_ctr;
   reg [15:0] frame_len_ctr;
   reg [7:0] pause_ctr, pause_dat;

   wire in_ifg 		     = (ifg_ctr != 0);

   wire [31:0] crc_out;
   
   localparam MIN_FRAME_LEN  = 64 + 8 - 4; // Min frame length includes preamble but not CRC
   localparam MAX_FRAME_LEN  = 8192;       // How big are the jumbo frames we want to handle?
   always @(posedge tx_clk)
     if(reset |(tx_state == TX_IDLE))
       frame_len_ctr 	    <= 0;
     else
       frame_len_ctr 	    <= frame_len_ctr + 1;
   
   localparam TX_IDLE 	     = 0;
   localparam TX_PREAMBLE    = 1;
   localparam TX_SOF_DEL     = TX_PREAMBLE + 7;
   localparam TX_FIRSTBYTE   = TX_SOF_DEL + 1;
   localparam TX_IN_FRAME    = TX_FIRSTBYTE + 1;
   localparam TX_IN_FRAME_2  = TX_IN_FRAME + 1;
   localparam TX_PAD 	     = TX_IN_FRAME_2 + 1;
   localparam TX_CRC_0 	     = 16;
   localparam TX_CRC_1 	     = TX_CRC_0 + 1;
   localparam TX_CRC_2 	     = TX_CRC_0 + 2;
   localparam TX_CRC_3 	     = TX_CRC_0 + 3;
   localparam TX_ERROR 	     = 32;
   localparam TX_PAUSE 	     = 55;
   localparam TX_PAUSE_SOF   = TX_PAUSE + 7;
   localparam TX_PAUSE_FIRST = TX_PAUSE_SOF + 1;
   localparam TX_PAUSE_END   = TX_PAUSE_SOF + 18;

   reg send_pause;
   reg [15:0] pause_time_held;

   always @(posedge tx_clk)
     if(reset)
       send_pause      <= 0;
     else if(pause_req)
       send_pause      <= 1;
     else if(tx_state == TX_PAUSE)
       send_pause      <= 0;

   always @(posedge tx_clk)
     if(pause_req)
       pause_time_held <= pause_time;
   
   always @(posedge tx_clk)
     if(reset)
       tx_state        <= TX_IDLE;
     else 
       case(tx_state)
	 TX_IDLE :
	   if(~in_ifg)
	     if(send_pause)
	       tx_state <= TX_PAUSE;
	     else if(tx_valid & ~pause_apply)
	       tx_state <= TX_PREAMBLE;
	 TX_FIRSTBYTE :
	   if(tx_error)
	     tx_state <= TX_ERROR;   // underrun
	   else if(~tx_valid)
	     tx_state <= TX_PAD;
	   else
	     tx_state <= TX_IN_FRAME;
	 TX_IN_FRAME :
	   if(tx_error)
	     tx_state <= TX_ERROR;   // underrun
	   else if(~tx_valid)
	     tx_state <= TX_PAD;
	   else if(frame_len_ctr == MIN_FRAME_LEN - 1)
	     tx_state <= TX_IN_FRAME_2;
	 TX_IN_FRAME_2 :
	   if(tx_error)
	     tx_state <= TX_ERROR;   // underrun
	   else if(~tx_valid)
	     tx_state <= TX_CRC_0;
	 TX_PAD :
	   if(frame_len_ctr == MIN_FRAME_LEN)
	     tx_state <= TX_CRC_0;
	 TX_CRC_3 :
	   tx_state <= TX_IDLE;
	 TX_ERROR :
	   tx_state <= TX_IDLE;
	 TX_PAUSE_END :
	   tx_state <= TX_PAD;
	 default :
	   tx_state <= tx_state + 1;
       endcase // case (tx_state)

   always @(posedge tx_clk)
     if(reset)
       begin
	  tx_en_pre <= 0;
	  tx_er_pre <= 0;
	  txd_pre   <= 0;
       end
     else
       casex(tx_state)
	 TX_IDLE :
	   begin
	      tx_en_pre <= 0;
	      tx_er_pre <= 0;
	      txd_pre <= 0;
	   end
	 TX_PREAMBLE, TX_PAUSE :
	   begin
	      txd_pre 	 <= 8'h55;
	      tx_en_pre <= 1;
	   end
	 TX_SOF_DEL, TX_PAUSE_SOF :
	   txd_pre <= 8'hD5;
	 TX_FIRSTBYTE, TX_IN_FRAME, TX_IN_FRAME_2 :
	   txd_pre <= tx_valid ? tx_data : 0;
	 TX_ERROR :
	   begin
	      tx_er_pre <= 1;
	      txd_pre 	 <= 0;
	   end
	 TX_CRC_3 :
	   tx_en_pre <= 0;
	 TX_PAD :
	   txd_pre <= 0;
	 TX_PAUSE_FIRST, 8'b01xx_xxxx :  // In Pause Frame
	   txd_pre <= pause_dat;
       endcase // case (tx_state)

   localparam SGE_FLOW_CTRL_ADDR  = 48'h01_80_C2_00_00_01;
   always @(posedge tx_clk)
     case(tx_state)
       TX_PAUSE_SOF :
	 pause_dat    <= SGE_FLOW_CTRL_ADDR[47:40];  // Note everything must be 1 cycle early
       TX_PAUSE_SOF + 1:
	 pause_dat    <= SGE_FLOW_CTRL_ADDR[39:32];
       TX_PAUSE_SOF + 2:
	 pause_dat    <= SGE_FLOW_CTRL_ADDR[31:24];
       TX_PAUSE_SOF + 3:
	 pause_dat    <= SGE_FLOW_CTRL_ADDR[23:16];
       TX_PAUSE_SOF + 4:
	 pause_dat    <= SGE_FLOW_CTRL_ADDR[15:8];
       TX_PAUSE_SOF + 5:
	 pause_dat    <= SGE_FLOW_CTRL_ADDR[7:0];
       TX_PAUSE_SOF + 6:
	 pause_dat    <= mac_addr[47:40];
       TX_PAUSE_SOF + 7:
	 pause_dat    <= mac_addr[39:32];
       TX_PAUSE_SOF + 8:
	 pause_dat    <= mac_addr[31:24];
       TX_PAUSE_SOF + 9:
	 pause_dat    <= mac_addr[23:16];
       TX_PAUSE_SOF + 10:
	 pause_dat    <= mac_addr[15:8];
       TX_PAUSE_SOF + 11:
	 pause_dat <= mac_addr[7:0];
       TX_PAUSE_SOF + 12:
	 pause_dat <= 8'h88;   // Type = 8808 = MAC ctrl frame
       TX_PAUSE_SOF + 13:
	 pause_dat <= 8'h08;
       TX_PAUSE_SOF + 14:
	 pause_dat <= 8'h00;   // Opcode = 0001 = PAUSE
       TX_PAUSE_SOF + 15:
	 pause_dat <= 8'h01;
       TX_PAUSE_SOF + 16:
	 pause_dat <= pause_time_held[15:8];
       TX_PAUSE_SOF + 17:
	 pause_dat <= pause_time_held[7:0];
     endcase // case (tx_state)
   
   wire start_ifg   = (tx_state == TX_CRC_3);
   always @(posedge tx_clk)
     if(reset)
       ifg_ctr 	   <= 100;
     else if(start_ifg)
       ifg_ctr 	   <= ifg;
     else if(ifg_ctr != 0)
       ifg_ctr 	   <= ifg_ctr - 1;

   wire clear_crc   = (tx_state == TX_IDLE);

   wire calc_crc    = 
	(tx_state==TX_IN_FRAME) |
	(tx_state==TX_IN_FRAME_2) |
	(tx_state==TX_PAD) |
	(tx_state[6]);

   crc crc(.clk(tx_clk), .reset(reset), .clear(clear_crc),
	    .data(txd_pre), .calc(calc_crc), .crc_out(crc_out));

   assign tx_ack    = (tx_state == TX_FIRSTBYTE);

   always @(posedge tx_clk) 
     begin
	GMII_TX_EN <= tx_en_pre;
	GMII_TX_ER <= tx_er_pre;
	case(tx_state)
	  TX_CRC_0 :
	    GMII_TXD <= crc_out[31:24];
	  TX_CRC_1 :
	    GMII_TXD <= crc_out[23:16];
	  TX_CRC_2 :
	    GMII_TXD <= crc_out[15:8];
	  TX_CRC_3 :
	    GMII_TXD <= crc_out[7:0];
	  default :
	    GMII_TXD <= txd_pre;
	endcase // case (tx_state)
     end

   // report that we are paused only when we get back to IDLE
   always @(posedge tx_clk)
     if(reset)
       paused 	     <= 0;
     else if(~pause_apply)
       paused 	     <= 0;
     else if(tx_state == TX_IDLE)
       paused 	     <= 1;
   
endmodule // simple_gemac_tx

// Testing code
/*
    reg [7:0] crc_ctr;
   reg calc_crc_d1;
   always @(posedge tx_clk) 
     calc_crc_d1 <= calc_crc;
   
   always @(posedge tx_clk)
     if(reset)
       crc_ctr 	 <= 0;
     else if(calc_crc)
       crc_ctr 	 <= crc_ctr+1;
     else if(calc_crc_d1)
       $display("CRC COUNT = %d",crc_ctr);
     else
       crc_ctr <= 0;
*/