mirror of
https://github.com/MiSTer-devel/VT52_MiSTer.git
synced 2026-04-19 03:05:47 +00:00
update readme.md with new features add key repeat add uart election a bit of work on video timing (still needs more work)
395 lines
13 KiB
Verilog
395 lines
13 KiB
Verilog
/* ================================================================
|
|
* VT52
|
|
*
|
|
* Copyright (C) 2024 Fred Van Eijk
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person
|
|
* obtaining a copy of this software and associated documentation
|
|
* files (the "Software"), to deal in the Software without
|
|
* restriction, including without limitation the rights to use,
|
|
* copy, modify, merge, publish, distribute, sublicense, and/or
|
|
* sell copies of the Software, and to permit persons to whom
|
|
* the Software is furnished to do so, subject to the following
|
|
* conditions:
|
|
*
|
|
* The above copyright notice and this permission notice shall be
|
|
* included in all copies or substantial portions of the Software.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
|
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
|
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
|
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
|
* OTHER DEALINGS IN THE SOFTWARE.
|
|
* ================================================================
|
|
*/
|
|
|
|
module command_handler
|
|
#(parameter ROWS = 24, // Visible rows
|
|
parameter COLS = 80,
|
|
parameter ROW_BITS = 5,
|
|
parameter COL_BITS = 7,
|
|
parameter ADDR_BITS = 11)
|
|
(
|
|
input wire clk,
|
|
input wire reset,
|
|
input wire [7:0] data,
|
|
input wire valid,
|
|
input wire from_uart,
|
|
output reg ready,
|
|
output reg buffer_scroll,
|
|
input wire scroll_busy,
|
|
input wire scroll_done,
|
|
output reg [7:0] buffer_write_char,
|
|
output reg [ADDR_BITS-1:0] buffer_write_addr,
|
|
output reg buffer_write_enable,
|
|
output reg [COL_BITS-1:0] new_cursor_x,
|
|
output reg [ROW_BITS-1:0] new_cursor_y,
|
|
output reg new_cursor_wen
|
|
);
|
|
|
|
reg [ROW_BITS-1:0] new_row;
|
|
reg [COL_BITS-1:0] new_col;
|
|
reg [ADDR_BITS-1:0] current_row_addr;
|
|
reg [ADDR_BITS-1:0] current_char_addr;
|
|
reg [ADDR_BITS-1:0] erase_address;
|
|
reg [ADDR_BITS-1:0] last_char_to_erase;
|
|
reg [31:0] timeout_counter;
|
|
reg state_timeout;
|
|
reg update_cursor; // New: Flag to update cursor position
|
|
|
|
// State encoding - one-hot
|
|
localparam state_char = 8'b00000001;
|
|
localparam state_esc = 8'b00000010;
|
|
localparam state_row = 8'b00000100;
|
|
localparam state_col = 8'b00001000;
|
|
localparam state_addr = 8'b00010000;
|
|
localparam state_cursor = 8'b00100000;
|
|
localparam state_erase = 8'b01000000;
|
|
localparam state_scroll_wait = 8'b10000000;
|
|
|
|
reg [7:0] state;
|
|
|
|
// At 25MHz clock:
|
|
localparam UART_TIMEOUT = 32'd25_000_000; // 1 second
|
|
localparam KEYBOARD_TIMEOUT = 32'd125_000_000; // 5 seconds
|
|
|
|
// Timeout counter
|
|
always @(posedge clk) begin
|
|
if (reset) begin
|
|
timeout_counter <= 0;
|
|
state_timeout <= 0;
|
|
end
|
|
else begin
|
|
if (valid) begin
|
|
timeout_counter <= 0;
|
|
state_timeout <= 0;
|
|
end
|
|
else if (state == state_esc || state == state_row || state == state_col) begin
|
|
if (from_uart) begin
|
|
if (timeout_counter >= UART_TIMEOUT) begin
|
|
state_timeout <= 1;
|
|
end
|
|
else begin
|
|
timeout_counter <= timeout_counter + 1;
|
|
end
|
|
end
|
|
else begin
|
|
if (timeout_counter >= KEYBOARD_TIMEOUT) begin
|
|
state_timeout <= 1;
|
|
end
|
|
else begin
|
|
timeout_counter <= timeout_counter + 1;
|
|
end
|
|
end
|
|
end
|
|
else begin
|
|
timeout_counter <= 0;
|
|
state_timeout <= 0;
|
|
end
|
|
end
|
|
end
|
|
|
|
// Ready signal generation
|
|
wire in_multistate_operation = (state & (state_erase | state_cursor | state_addr | state_scroll_wait)) != 0;
|
|
|
|
// Ready signal management
|
|
always @(posedge clk) begin
|
|
if (reset) begin
|
|
ready <= 1'b1;
|
|
end
|
|
else begin
|
|
ready <= 1'b1; // Default to ready
|
|
|
|
if (scroll_busy) begin
|
|
ready <= 1'b0;
|
|
end
|
|
else if (valid && ready) begin // Only drop ready for one cycle when accepting data
|
|
ready <= 1'b0;
|
|
end
|
|
else if (in_multistate_operation && state != state_char) begin
|
|
ready <= 1'b0;
|
|
end
|
|
end
|
|
end
|
|
|
|
// Main state machine
|
|
always @(posedge clk) begin
|
|
if (reset) begin
|
|
buffer_write_char <= 0;
|
|
buffer_write_addr <= 0;
|
|
buffer_write_enable <= 0;
|
|
buffer_scroll <= 0;
|
|
new_cursor_x <= 0;
|
|
new_cursor_y <= 0;
|
|
new_cursor_wen <= 0;
|
|
current_row_addr <= 0;
|
|
current_char_addr <= 0;
|
|
state <= state_char;
|
|
new_row <= 0;
|
|
new_col <= 0;
|
|
erase_address <= 0;
|
|
last_char_to_erase <= 0;
|
|
update_cursor <= 0;
|
|
end
|
|
else begin
|
|
// Clear one-cycle signals
|
|
buffer_write_enable <= 0;
|
|
new_cursor_wen <= update_cursor; // Set based on update flag
|
|
update_cursor <= 0; // Clear the update flag
|
|
buffer_scroll <= 0;
|
|
|
|
case (state)
|
|
state_scroll_wait: begin
|
|
if (scroll_done || !scroll_busy) begin
|
|
current_row_addr <= (ROWS-1) * COLS;
|
|
current_char_addr <= (ROWS-1) * COLS + new_cursor_x;
|
|
state <= state_char;
|
|
end
|
|
end
|
|
|
|
state_erase: begin
|
|
if (!scroll_busy) begin
|
|
if (erase_address > last_char_to_erase) begin
|
|
state <= state_char;
|
|
end
|
|
else begin
|
|
buffer_write_char <= 8'h20;
|
|
buffer_write_addr <= erase_address;
|
|
erase_address <= erase_address + 1;
|
|
buffer_write_enable <= 1;
|
|
end
|
|
end
|
|
end
|
|
|
|
state_char: begin
|
|
if (ready && valid && !scroll_busy) begin
|
|
if (data >= 8'h20 && data != 8'h7f) begin
|
|
// Write the character first
|
|
buffer_write_char <= data;
|
|
buffer_write_addr <= current_char_addr;
|
|
buffer_write_enable <= 1;
|
|
|
|
// Then handle cursor movement
|
|
if (new_cursor_x == (COLS-1)) begin
|
|
if (new_cursor_y == (ROWS-1)) begin
|
|
buffer_scroll <= 1;
|
|
state <= state_scroll_wait;
|
|
end
|
|
else begin
|
|
new_cursor_y <= new_cursor_y + 1;
|
|
new_cursor_x <= 0;
|
|
update_cursor <= 1; // Set flag to update cursor
|
|
current_row_addr <= current_row_addr + COLS;
|
|
current_char_addr <= current_row_addr + COLS;
|
|
end
|
|
end
|
|
else begin
|
|
new_cursor_x <= new_cursor_x + 1;
|
|
current_char_addr <= current_char_addr + 1;
|
|
update_cursor <= 1; // Set flag to update cursor
|
|
end
|
|
end
|
|
else begin
|
|
case (data)
|
|
8'h08: begin // BS - Move cursor left (no erase)
|
|
if (new_cursor_x != 0) begin
|
|
new_cursor_x <= new_cursor_x - 1;
|
|
current_char_addr <= current_char_addr - 1;
|
|
update_cursor <= 1;
|
|
end
|
|
end
|
|
|
|
8'h09: begin // HT - Tab (every 8 columns)
|
|
if (new_cursor_x < (COLS-9)) begin
|
|
new_cursor_x <= {(new_cursor_x[COL_BITS-1:3]+1), 3'b000};
|
|
current_char_addr <= {(current_char_addr[ADDR_BITS-1:3]+1), 3'b000};
|
|
update_cursor <= 1;
|
|
end
|
|
else if (new_cursor_x != (COLS-1)) begin
|
|
new_cursor_x <= new_cursor_x + 1;
|
|
current_char_addr <= current_char_addr + 1;
|
|
update_cursor <= 1;
|
|
end
|
|
end
|
|
|
|
8'h0a: begin // LF - Line feed
|
|
if (new_cursor_y == (ROWS-1)) begin
|
|
buffer_scroll <= 1;
|
|
state <= state_scroll_wait;
|
|
end
|
|
else begin
|
|
new_cursor_y <= new_cursor_y + 1;
|
|
update_cursor <= 1;
|
|
current_row_addr <= current_row_addr + COLS;
|
|
current_char_addr <= current_char_addr + COLS;
|
|
end
|
|
end
|
|
|
|
8'h0d: begin // CR - Return to start of line
|
|
new_cursor_x <= 0;
|
|
update_cursor <= 1;
|
|
current_char_addr <= current_row_addr;
|
|
end
|
|
|
|
8'h1b: begin // ESC - Start escape sequence
|
|
state <= state_esc;
|
|
end
|
|
endcase
|
|
end
|
|
end
|
|
end
|
|
|
|
state_esc: begin
|
|
if (valid && !scroll_busy) begin
|
|
case (data)
|
|
"A": begin // Cursor up
|
|
if (new_cursor_y != 0) begin
|
|
new_cursor_y <= new_cursor_y - 1;
|
|
update_cursor <= 1;
|
|
current_row_addr <= current_row_addr - COLS;
|
|
current_char_addr <= current_char_addr - COLS;
|
|
end
|
|
state <= state_char;
|
|
end
|
|
|
|
"B": begin // Cursor down
|
|
if (new_cursor_y != (ROWS-1)) begin
|
|
new_cursor_y <= new_cursor_y + 1;
|
|
update_cursor <= 1;
|
|
current_row_addr <= current_row_addr + COLS;
|
|
current_char_addr <= current_char_addr + COLS;
|
|
end
|
|
state <= state_char;
|
|
end
|
|
|
|
"C": begin // Cursor right
|
|
if (new_cursor_x != (COLS-1)) begin
|
|
new_cursor_x <= new_cursor_x + 1;
|
|
update_cursor <= 1;
|
|
current_char_addr <= current_char_addr + 1;
|
|
end
|
|
state <= state_char;
|
|
end
|
|
|
|
"D": begin // Cursor left
|
|
if (new_cursor_x != 0) begin
|
|
new_cursor_x <= new_cursor_x - 1;
|
|
update_cursor <= 1;
|
|
current_char_addr <= current_char_addr - 1;
|
|
end
|
|
state <= state_char;
|
|
end
|
|
|
|
"H": begin // Cursor home
|
|
new_cursor_x <= 0;
|
|
new_cursor_y <= 0;
|
|
update_cursor <= 1;
|
|
current_row_addr <= 0;
|
|
current_char_addr <= 0;
|
|
state <= state_char;
|
|
end
|
|
|
|
"I": begin // Reverse line feed
|
|
if (new_cursor_y == 0) begin
|
|
buffer_scroll <= 1;
|
|
state <= state_scroll_wait;
|
|
end
|
|
else begin
|
|
new_cursor_y <= new_cursor_y - 1;
|
|
update_cursor <= 1;
|
|
current_row_addr <= current_row_addr - COLS;
|
|
current_char_addr <= current_char_addr - COLS;
|
|
state <= state_char;
|
|
end
|
|
end
|
|
|
|
"J": begin // Erase to end of screen and home cursor
|
|
buffer_write_char <= 8'h20;
|
|
erase_address <= 0;
|
|
last_char_to_erase <= (ROWS * COLS) - 1;
|
|
buffer_write_enable <= 1;
|
|
// Also move cursor home per VT52 spec
|
|
new_cursor_x <= 0;
|
|
new_cursor_y <= 0;
|
|
update_cursor <= 1;
|
|
current_row_addr <= 0;
|
|
current_char_addr <= 0;
|
|
state <= state_erase;
|
|
end
|
|
|
|
"K": begin // Erase to end of line (cursor doesn't move)
|
|
buffer_write_char <= 8'h20;
|
|
erase_address <= current_char_addr;
|
|
last_char_to_erase <= current_row_addr + (COLS-1);
|
|
buffer_write_enable <= 1;
|
|
state <= state_erase;
|
|
end
|
|
|
|
"Y": begin // Direct cursor address
|
|
state <= state_row;
|
|
end
|
|
|
|
default: begin
|
|
state <= state_char; // Ignore unknown escape sequences
|
|
end
|
|
endcase
|
|
end
|
|
|
|
if (state_timeout) begin
|
|
state <= state_char;
|
|
end
|
|
end
|
|
|
|
state_row: begin
|
|
if (valid) begin
|
|
new_row <= (data >= 8'h20 && data < (8'h20 + ROWS)) ?
|
|
data - 8'h20 : new_cursor_y;
|
|
state <= state_col;
|
|
end
|
|
end
|
|
|
|
state_col: begin
|
|
if (valid) begin
|
|
new_col <= (data >= 8'h20 && data < (8'h20 + COLS)) ?
|
|
data - 8'h20 : new_cursor_x;
|
|
state <= state_cursor;
|
|
end
|
|
end
|
|
|
|
state_cursor: begin
|
|
new_cursor_x <= new_col;
|
|
new_cursor_y <= new_row;
|
|
update_cursor <= 1;
|
|
current_row_addr <= new_row * COLS;
|
|
current_char_addr <= (new_row * COLS) + new_col;
|
|
state <= state_char;
|
|
end
|
|
|
|
endcase
|
|
end
|
|
end
|
|
|
|
endmodule |