vt52-fpga  1.0.0 Initial
vt52-fpga is a serial terminal implemented on a FPGA
command_handler.v
Go to the documentation of this file.
1 // TODO define constants for the control chars
2 module command_handler
3  #(parameter ROWS = 24,
4  parameter COLS = 80,
5  parameter LAST_ROW = (ROWS-1) * COLS,
6  parameter ONE_PAST_LAST_ROW = ROWS * COLS,
7  parameter ROW_BITS = 5,
8  parameter COL_BITS = 7,
9  parameter ADDR_BITS = 11)
10  (
11  input clk,
12  input reset,
13  input [7:0] data,
14  input valid,
15  output ready,
16  output [ADDR_BITS-1:0] new_first_char,
17  output new_first_char_wen,
18  output [7:0] new_char,
19  output [ADDR_BITS-1:0] new_char_address,
20  output new_char_wen,
21  output [COL_BITS-1:0] new_cursor_x,
22  output [ROW_BITS-1:0] new_cursor_y,
23  output new_cursor_wen,
24  output graphic_mode
25  );
26 
27  // XXX maybe ready should be registered? reg ready_q;
28  reg [7:0] new_char_q;
29  reg [ADDR_BITS-1:0] new_char_address_q;
30  reg new_char_wen_q;
31  reg [COL_BITS-1:0] new_cursor_x_q;
32  reg [ROW_BITS-1:0] new_cursor_y_q;
33  reg new_cursor_wen_q;
34  reg [ADDR_BITS-1:0] new_first_char_q;
35  reg new_first_char_wen_q;
36 
37  reg [ROW_BITS-1:0] new_row;
38  reg [COL_BITS-1:0] new_col;
39  // This may temporarily hold a value that's twice the size of a regular
40  // address (we adjust it at a later state)
41  // so we DON'T substract 1 from ADDR_BITS
42  reg [ADDR_BITS:0] new_addr;
43  reg [ADDR_BITS-1:0] last_char_to_erase;
44 
45  reg [ADDR_BITS-1:0] current_row_addr;
46  reg [ADDR_BITS-1:0] current_char_addr;
47 
48  // state: one hot encoding
49  // char is the normal state
50  // row, col, addr & cursor form a pipeline for Esc-Y
51  // no input accepted on addr & cursor
52  // erase is for the erase loop, no input is accepted on this state
53  localparam state_char = 8'b00000001;
54  localparam state_esc = 8'b00000010;
55  localparam state_row = 8'b00000100;
56  localparam state_col = 8'b00001000;
57  localparam state_addr = 8'b00010000;
58  localparam state_cursor = 8'b00100000;
59  localparam state_erase = 8'b01000000;
60 
61  reg [7:0] state;
62  reg graphic_mode_set;
63 
64  // if we are erasing part of the screen or moving the cursor
65  // we can't receive new commands
66  assign ready = (state & (state_erase | state_cursor | state_addr)) == 0;
67  assign new_char = new_char_q;
68  assign new_char_address = new_char_address_q;
69  assign new_char_wen = new_char_wen_q;
70  assign new_cursor_x = new_cursor_x_q;
71  assign new_cursor_y = new_cursor_y_q;
72  assign new_cursor_wen = new_cursor_wen_q;
73  assign new_first_char = new_first_char_q;
74  assign new_first_char_wen = new_first_char_wen_q;
75  assign graphic_mode = graphic_mode_set;
76 
77  always @(posedge clk) begin
78  if (reset) begin
79  new_char_q <= 0;
80  new_char_address_q <= 0;
81  new_char_wen_q <= 0;
82 
83  new_cursor_x_q <= 0;
84  new_cursor_y_q <= 0;
85  new_cursor_wen_q <= 0;
86 
87  current_row_addr <= 0;
88  current_char_addr <= 0;
89 
90  new_first_char_q <= 0;
91  new_first_char_wen_q <= 0;
92 
93  state <= state_char;
94  new_row <= 0;
95  new_col <= 0;
96  new_addr <= 0;
97  last_char_to_erase <= 0;
98  graphic_mode_set <= 0;
99  end
100  else begin
101  // after one clock cycle we should turn these off
102  if (new_char_wen_q) new_char_wen_q <= 0;
103  if (new_cursor_wen_q) new_cursor_wen_q <= 0;
104  if (new_first_char_wen_q) new_first_char_wen_q <= 0;
105  // first the states that consume input
106  if (ready && valid) begin
107  case (state)
108  state_char: begin
109  // new char arrived
110  if (data >= 8'h20 && data != 8'h7f) begin
111  // printable char, easy
112  new_char_q <= data;
113  new_char_address_q <= current_char_addr;
114  new_char_wen_q <= 1;
115  // no auto linefeed
116  if (new_cursor_x_q != (COLS-1)) begin
117  new_cursor_x_q <= new_cursor_x_q + 1;
118  current_char_addr <= current_char_addr + 1;
119  new_cursor_wen_q <= 1;
120  end
121  end
122  else begin
123  case (data)
124  // backspace
125  8'h08: begin
126  if (new_cursor_x_q != 0) begin
127  new_cursor_x_q <= new_cursor_x_q - 1;
128  current_char_addr <= current_char_addr - 1;
129  new_cursor_wen_q <= 1;
130  end
131  end
132  // tab
133  8'h09: begin
134  // go until the last tab stop by 8 spaces, then 1 by 1
135  if (new_cursor_x_q < (COLS-9)) begin
136  new_cursor_x_q <= {(new_cursor_x_q[COL_BITS-1:3]+1), 3'b000};
137  current_char_addr <= {(current_char_addr[ADDR_BITS-1:3]+1), 3'b000};
138  new_cursor_wen_q <= 1;
139  end
140  else if (new_cursor_x_q != (COLS-1)) begin
141  new_cursor_x_q <= new_cursor_x_q + 1;
142  current_char_addr <= current_char_addr + 1;
143  new_cursor_wen_q <= 1;
144  end
145  end // case: 8'h09
146  // linefeed
147  8'h0a: begin
148  if (new_cursor_y_q == (ROWS-1)) begin
149  new_first_char_q <= new_first_char_q == LAST_ROW?
150  0 : new_first_char_q + COLS;
151 
152  if (current_row_addr == LAST_ROW) begin
153  current_row_addr <= 0;
154  current_char_addr <= new_cursor_x_q;
155  end
156  else begin
157  current_row_addr <= current_row_addr + COLS;
158  current_char_addr <= current_char_addr + COLS;
159  end
160  new_first_char_wen_q <= 1;
161  // characters to erase last line
162  new_char_q <= " ";
163  new_char_address_q <= new_first_char_q;
164  new_char_wen_q <= 1;
165  last_char_to_erase <= new_first_char_q + (COLS-1);
166  state <= state_erase;
167  end
168  else begin
169  new_cursor_y_q <= new_cursor_y_q + 1;
170  new_cursor_wen_q <= 1;
171  if (current_row_addr == LAST_ROW) begin
172  current_row_addr <= 0;
173  current_char_addr <= new_cursor_x_q;
174  end
175  else begin
176  current_row_addr <= current_row_addr + COLS;
177  current_char_addr <= current_char_addr + COLS;
178  end
179  end
180  end
181  // carriage return
182  8'h0d: begin
183  if (new_cursor_x != 0) begin
184  new_cursor_x_q <= 0;
185  new_cursor_wen_q <= 1;
186  current_char_addr <= current_row_addr;
187  end
188  end
189  // escape
190  8'h1b: begin
191  state <= state_esc;
192  end
193  endcase // case (data)
194  end // else: !if(data >= 8'h20 && data <= 8'h7e)
195  end // case: state_char
196  state_esc: begin
197  case (data)
198  // Basic cursor movement
199  // Esc-only, so no BS, LF & SPACE (covered before)
200  "B": begin
201  if (new_cursor_y_q != (ROWS-1)) begin
202  new_cursor_y_q <= new_cursor_y_q + 1;
203  new_cursor_wen_q <= 1;
204  if (current_row_addr == LAST_ROW) begin
205  current_row_addr <= 0;
206  current_char_addr <= new_cursor_x_q;
207  end
208  else begin
209  current_row_addr <= current_row_addr + COLS;
210  current_char_addr <= current_char_addr + COLS;
211  end
212  end
213  state <= state_char;
214  end
215  "I": begin
216  if (new_cursor_y_q == 0) begin
217  if (new_first_char_q == 0) begin
218  new_first_char_q <= LAST_ROW;
219  current_row_addr <= LAST_ROW;
220  current_char_addr <= LAST_ROW + new_cursor_x_q;
221  // characters to erase (whole line)
222  new_char_address_q <= LAST_ROW;
223  last_char_to_erase <= LAST_ROW+(COLS-1);
224  end
225  else begin
226  new_first_char_q <= new_first_char_q - COLS;
227  current_row_addr <= current_row_addr - COLS;
228  current_char_addr <= current_char_addr - COLS;
229  // characters to erase (whole line)
230  new_char_address_q <= new_first_char_q - COLS;
231  last_char_to_erase <= new_first_char_q - 1;
232  end
233  new_first_char_wen_q <= 1;
234  // character to erase last line
235  new_char_q <= " ";
236  new_char_wen_q <= 1;
237  state <= state_erase;
238  end
239  else begin
240  new_cursor_y_q <= new_cursor_y_q - 1;
241  new_cursor_wen_q <= 1;
242  if (current_row_addr == 0) begin
243  current_row_addr <= LAST_ROW;
244  current_char_addr <= LAST_ROW + new_cursor_x_q;
245  end
246  else begin
247  current_row_addr <= current_row_addr - COLS;
248  current_char_addr <= current_char_addr - COLS;
249  end
250  state <= state_char;
251  end
252  end
253  "A": begin
254  if (new_cursor_y_q != 0) begin
255  new_cursor_y_q <= new_cursor_y_q - 1;
256  new_cursor_wen_q <= 1;
257  if (current_row_addr == 0) begin
258  current_row_addr <= LAST_ROW;
259  current_char_addr <= LAST_ROW + new_cursor_x_q;
260  end
261  else begin
262  current_row_addr <= current_row_addr - COLS;
263  current_char_addr <= current_char_addr - COLS;
264  end
265  end
266  state <= state_char;
267  end
268  "C": begin
269  if (new_cursor_x_q != (COLS-1)) begin
270  new_cursor_x_q <= new_cursor_x_q + 1;
271  new_cursor_wen_q <= 1;
272  current_char_addr <= current_char_addr+1;
273  end
274  state <= state_char;
275  end
276  "D": begin
277  if (new_cursor_x_q != 0) begin
278  new_cursor_x_q <= new_cursor_x_q - 1;
279  new_cursor_wen_q <= 1;
280  current_char_addr <= current_char_addr-1;
281  end
282  state <= state_char;
283  end
284  // Advanced cursor movement
285  // Esc-only, so no CR & TAB (covered before)
286  "H": begin
287  new_cursor_x_q <= 0;
288  new_cursor_y_q <= 0;
289  new_cursor_wen_q <= 1;
290  current_row_addr <= new_first_char_q;
291  current_char_addr <= new_first_char_q;
292  state <= state_char;
293  end
294  "Y": begin
295  // "Y" received, expecting row & col
296  state <= state_row;
297  end
298  // Screen erasure
299  "K": begin
300  // erase to end of line
301  new_char_q <= " ";
302  new_char_address_q <= current_char_addr;
303  new_char_wen_q <= 1;
304  last_char_to_erase <= current_row_addr + (COLS-1);
305  state <= state_erase;
306  end
307  "J": begin
308  // erase to end of screen
309  new_char_q <= " ";
310  new_char_address_q <= current_char_addr;
311  new_char_wen_q <= 1;
312  last_char_to_erase <= new_first_char_q == 0?
313  LAST_ROW+(COLS-1): new_first_char_q-1;
314  state <= state_erase;
315  end
316  // Graphic mode
317  "F": begin // enter
318  graphic_mode_set <= 1;
319  state <= state_char;
320  end
321  "G": begin // exit
322  graphic_mode_set <= 0;
323  state <= state_char;
324  end
325 
326  // escape
327  8'h1b: begin
328  // on VT52 two escapes don't cancel each other
329  // do nothing
330  end
331  default: begin
332  // unrecognized escape sequence, back to normal
333  state <= state_char;
334  end
335  endcase // case (data)
336  end // case: state_esc
337  state_row: begin
338  // row received, now we need col
339  new_row <= (data >= 8'h20 && data < (8'h20 + ROWS))?
340  data - 8'h20 : new_cursor_y;
341  state <= state_col;
342  end
343  state_col: begin
344  // row & col received, now we need to calculate the new row address
345  // XXX I'm not sure what happens if data < 8'h20, this is a guess
346  new_col <= (data >= 8'h20 && data < (8'h20 + COLS))?
347  data - 8'h20 : (COLS-1);
348  // this may need substracting if it's more than LAST_ROW
349  // but we'll do it in the next state
350  // new_addr has an extra bit to avoid overflows
351  new_addr <= new_row * 80 + new_first_char_q;
352  state <= state_addr;
353  end
354  // states erase, addr & cursor aren't here as they don't consume input
355  endcase // case (state)
356  end // if (ready && valid)
357  // now we can handle the states that don't consume input
358  else begin
359  case (state)
360  state_erase: begin
361  if (new_char_address_q == last_char_to_erase) begin
362  // all chars erased, resume normal operation
363  state <= state_char;
364  end
365  else begin
366  // keep erasing, but be careful if reaching the end of the buffer
367  new_char_address_q = new_char_address_q == LAST_ROW + (COLS+1)?
368  0 : new_char_address_q + 1;
369  new_char_wen_q <= 1;
370  end
371  end
372  state_addr: begin
373  // after possibly adjusting the address we are ready to
374  // move the cursor
375  new_addr <= new_addr > LAST_ROW? new_addr - ONE_PAST_LAST_ROW : new_addr;
376  state <= state_cursor;
377  end
378  state_cursor: begin
379  // move cursor and go back to idle
380  new_cursor_x_q <= new_col;
381  new_cursor_y_q <= new_row;
382  new_cursor_wen_q <= 1;
383  // once adjusted, we can ignore the higher bit
384  current_row_addr <= new_addr[ADDR_BITS-1:0];
385  current_char_addr <= new_addr + new_col;
386 
387  state <= state_char;
388  end // if (state == state_col)
389  endcase // case (state)
390  end // else: !if(ready && valid)
391  end // else: !if(reset)
392  end // always @ (posedge clk)
393 endmodule
module command_handler(input clk, input reset, input[8] data, input valid, output ready, output[ADDR_BITS-1:0] new_first_char, output new_first_char_wen, output[8] new_char, output[ADDR_BITS-1:0] new_char_address, output new_char_wen, output[COL_BITS-1:0] new_cursor_x, output[ROW_BITS-1:0] new_cursor_y, output new_cursor_wen, output graphic_mode)
always(posedge clk)
Definition: uart_rx.v:86