Простой текстовый терминал

DSCN5966

Собрав свою отладочную плату с FPGA, мне хотелось создать на ее базе что-то довольно простое и полезное. Я имел необходимость в неком информационном табло для вывода отладочной информации.

 

 

 

Самым популярным интерфейсом до сих пор является последовательный типа UART или SPI, его мы и будем использовать. Тогда куда выводить информацию? Конечно же VGA! Что может быть проще для ПЛИС?

Так как это именно текстовый терминал, его главная характеристика — количество символов на экране. Кроме того, он должен обладать функцией автопрокрутки, очистки и минимальной ASCII таблицей  символов.

Контроллер VGA

Я использовал готовый модуль с сайта Marsohod, да и изобретать его нет смысла, лишь модифицировать. Версия на языке Verilog:

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
module hvsync (
    // inputs:
    input wire char_clock,
 
    // outputs:
    output reg [11:0]char_count_, //post reg
    output reg [11:0]line_count_, //post reg
    output reg hsync,
    output reg vsync,
	output reg frame
    );
 
//VGA Standart
`define h_visible_area  640
`define h_front_porch   16
`define h_sync_pulse    96
`define h_back_porch    48
 
`define v_visible_area  400
`define v_front_porch   12
`define v_sync_pulse    2
`define v_back_porch    35
 
//variables
reg [11:0]char_count;
reg [1:0]pixel_state;
reg [11:0]line_count;
reg [1:0]line_state;
 
reg end_of_line;
reg end_of_frame;
 
//permanent comb computations:
always @*
begin
    //horizontal processing
    if(char_count < `h_visible_area)
        pixel_state = 0; //active video
    else
    if(char_count < `h_visible_area + `h_front_porch)
        pixel_state = 1; //front porch
    else
    if(char_count < `h_visible_area + `h_front_porch + `h_sync_pulse)
        pixel_state = 2; //hsync impuls
    else
        pixel_state = 3; //back porch
 
    if(char_count < `h_visible_area + `h_front_porch + `h_sync_pulse + `h_back_porch)
        end_of_line = 0;
    else
        end_of_line = 1;
 
    //vert processing
    if(line_count < `v_visible_area)
        line_state = 0; //active video lines
    else
    if(line_count < `v_visible_area + `v_front_porch)
        line_state = 1; //front porch
    else
    if(line_count < `v_visible_area + `v_front_porch + `v_sync_pulse)
        line_state = 2; //vsync impuls
    else
        line_state = 3; //front porch
 
    if(line_count < `v_visible_area + `v_front_porch + `v_sync_pulse + `v_back_porch)
        end_of_frame = 0;
    else
        end_of_frame = 1;            
end
 
//synchronous process
always @(posedge char_clock)
begin
    hsync <= (pixel_state==2'b10);
    vsync <= (line_state!=2'b10);
    frame <= (pixel_state==2'b0 && line_state==2'b0);
 
    //char_count_ <= char_count;
    line_count_ <= line_count;
 
    if(end_of_line)
    begin
        char_count <= 0;
        char_count_ <= 0;
 
        if(end_of_frame)
            line_count <= 0;
        else
            line_count <= line_count + 1'b1;
    end
    else
    begin
        char_count <= char_count + 1'b1;
        if (pixel_state==2'b0)
			char_count_ <= char_count_ + 1'b1;
    end
end
 
endmodule

Все, что от него требуется: синхроимпульсы и счетчик пикселей и строк, причем счетчик пикселей должен считать только видимую часть. Формат изображения задан следующий: 640×400@70Гц. Вы можете сделать больше, но на Cyclone I может не хватить памяти для символов.

Знакогенератор

Это самый важный модуль терминала, к нему также будет необходимо ОЗУ с символами и ПЗУ со шрифтом. Я предпочел стандартный размер шрифта 8×16 пикселей, в таком случае нам будет доступно 80×25 символов на экран.
scr
Фактически, это самая обыкновенная тайловая графическая система. Создавать некую функцию для прорисовки отдельного тайла мы не сможем, мы вынуждены рисовать вместе со сканирующем лучом монитора, непосредственно при выводе.

Aсимвол = (Xэкран div 8) + (Yэкран div 16) * 80

Xшрифт = Xэкран % 8

Yшрифт = Yэкран % 16

Для удобства лучше представить массив со шрифтом в виде одного столба, вместо матрицы. Естественно, каждый пиксель будет занимать 1 бит.

col

Остается прибавить к координатам шрифта смещение на = 8*16*[символ] и записать в линейном виде. Для скроллинга используем дополнительную переменную, которая будет суммироваться с адресом (Aсимвол) символа в памяти.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
`define h_symbols 80
`define v_symbols 25
 
module chars (
    input wire [11:0] char_count,
    input wire [11:0] line_count,
 
    input wire [8:0] char, //Symbol code
 
    input wire [10:0]scroll_line, //Scrolling
 
    output wire [14:0] font_addr, //Address of font bitmap
    output wire [10:0] addr, //Address of symbol
    output wire redink //Ink
    );
 
assign addr = ((char_count)>>3) + (((line_count>>4)+scroll_line)*`h_symbols); 
assign font_addr = ((char_count%8) + (line_count%16)*8) + ((char[7:0])<<7);
assign redink = ~char[8]; //Red/White ink
 
endmodule

Можете заметить, что знакогенератор полностью асинхронный. Я не вижу весомых причин делать его регистровым, частоты там все равно очень низкие. Кстати, я использовал специальный атрибут  в коде символа — еще один цвет для разнообразия (8 + 1 бит).

Печатный станок

Кто должен помещать символы в память, переводить строку, прокручивать? — Машина состояний.

Станок всегда ожидает команд от приемника, как только приходят данные, он декодирует их (если это спец.символ) и проводит соответствующие операции с памятью символов.

Пояснения лишь требует один странный пункт «Регенерация» — это заполнение пустотой всей следующей строки при вводе символа, чтобы при прокрутке старые данные не появлялись снизу.

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
`define h_symbols 80
`define v_symbols 25
 
module machine (
    input wire clock,
 
    input wire [7:0]in,
    input wire get,
 
    output reg [10:0]address,
    output reg wr,
    output reg [8:0]data,
 
    output reg [10:0]scroll_line
);
 
parameter idle = 0;
parameter decode = 1;
parameter changeink = 2;
parameter backspace = 3;
parameter write = 4;
parameter nextline = 5;
parameter flush = 6;
parameter inc = 7;
parameter nop = 8;
parameter refresh = 9;
 
//variables 
reg [7:0]buffer;
reg redink = 0;
reg fullscreen = 0;
 
reg [19:0]addrchar; //FixME
 
reg [7:0]count;
reg [3:0]state;
 
reg ext;
 
//synchronous process
always @(negedge clock)
begin
	case (state)
		idle:
			if (get)
				state <= decode;
 
		decode:
			case(buffer)
				0: //Null
					state <= idle;
				26: //Pause/Break
					state <= changeink;
				4: //Clear
					state <= flush;	
				127: //Backspace 
					state <= backspace;
				13: //Enter
					state <= nextline;
 
				default:
					state <= write;
			endcase
 
		refresh:
			if (count > 79)
				state <= idle;
 
		changeink:
			state <= idle;
 
		write:
			state <= inc;
 
		inc:
			state <= refresh;
 
		nop:
			state <= idle;
 
		nextline:
		if (!(addrchar%`h_symbols) && ext)
			state <= refresh;
 
		backspace:
			state <= nop;
 
		flush:
		if (addrchar == 0 && ext)
			state <= nop;
 
		default:
			state <= idle;
	endcase
end
 
always @(posedge clock)
begin
	case(state)
		idle:
		begin
			wr <= 0;
			buffer <= in;
			ext <= 0;
 
			if (count > 0)
			begin
				count <= 0;
				addrchar <= addrchar - `h_symbols;
			end
		end
 
		changeink:
			redink <= ~redink;
 
		write:
		begin
			data[7:0] <= buffer;
			data[8] <= redink; 
			wr <= 1;
		end
 
		inc:
		begin
			addrchar <= addrchar + 1;
			if (addrchar>(`h_symbols*`v_symbols)-3)
				fullscreen <= 1;
			if (fullscreen && !((addrchar+1)%`h_symbols))
				scroll_line <= scroll_line + 1;
			wr <= 0;
		end
 
		nextline:
		begin
			data <= 0;
			if (wr)
			begin
				addrchar <= addrchar + 1;
				ext <= 1;
			end
 
			if (addrchar>(`h_symbols*`v_symbols)-3)
				fullscreen <= 1;
			if (fullscreen && !((addrchar+1)%`h_symbols))
				scroll_line <= scroll_line + 1;	
 
			wr <= 1;
		end
 
		backspace:
		begin
			data <= 0;
			addrchar <= addrchar - 1;
			if (!(addrchar%`h_symbols) && fullscreen)
				scroll_line <= scroll_line - 1;
			wr <= 1;
		end
 
		refresh:
		begin
			data <= 0;
			addrchar <= addrchar + 1;
			count <= count + 1;
			wr <= 1;
		end
 
		flush:
		begin
			data <= 0;
			if (wr)
			begin 
				addrchar <= addrchar - 1;
				ext <= 1;
				scroll_line <= 0;
				fullscreen <= 0;
			end
			else
			begin
				addrchar <= 11'b11111111111;
			end
			wr <= 1;
		end		
	endcase
end
 
always @(posedge clock)
begin
	address <= addrchar;
end
 
endmodule

Машина работает на половинной частоте синхрогенератора.

Приемник

В данном случае — это UART, вы можете использовать SPI или что-угодно другое. Его код я взял отсюда. Можете заметить, что FIFO здесь никакого нету. Частота передачи — 115200 бод, поэтому буферы здесь ни к чему. 

Демонстрация

Текст вводится с компьютера через преобразователь.

Некоторые спец.команды для терминала:

  • 0x00 — пустой символ
  • 0x13 — перевод строки
  • 0x127 — стереть предыдущий символ
  • 0x04 — очистка экрана
  • 0x26 — смена чернил

Проект в Altera Quartus./

PS: В проекте есть небольшая ошибка: терминал может кончиться :-) Все дело в переменной addrchar, она должна циклически обнуляться. В скором времени я это исправлю.

Вы можите оставить комментарий, или поставить трэкбек со своего сайта.

Есть 5 коммент. к “Простой текстовый терминал”

  1. Sergey:

    Как увеличить размер букв в два, а то и больше, раза?

    Thumb up 0 Thumb down 0

    • Вы имеете в виду просто растянуть их? В таком случае можно обойтись без перерисовки шрифта.

      Уменьшайте количество строк/столбцов в 2 или более раз:

      1
      2
      
      `define h_symbols 80/2
      `define v_symbols 25/2

      Также со всеми вычислениями:

      1
      2
      3
      
      assign addr = ((char_count)>>3)/2 + (((line_count>>4)/2 + scroll_line)*`h_symbols); 
      assign font_addr = ((char_count%16)/2 + (line_count%32)*8)/2 + ((char[7:0])<<7);
      assign redink = ~char[8]; //Red/White ink

      Если не ошибаюсь, то так все должно работать.

      Thumb up 0 Thumb down 0

  2. Андрей:

    Не подскажете ли шрифт, который вы использовали.
    Судя по размеру знакогенератора, он должен быть чем то стандартным для 8х16. Однако мои попытки прикрутить свой шрифт закончились неудачно.
    В чём секрет? :)

    Thumb up 0 Thumb down 0

    • Шрифт я брал случайный, понравившийся из поиска goog. ;-)
      Затем в графическом редакторе превратил матрицу из символов в один столбец, шириной в 8 пикселей.

      Остальное — дело техники: есть редакторы, которые могут экспортировать монохроматические изображения в .bin/hex файлы. Такие файлы легко подключаются в Quartus в MIF-редакторе.

      Thumb up 0 Thumb down 0

  3. Андрей:

    Ага, а я просто подсовывал шрифт.
    Недопрочёл я про слолбец.
    Думал это получается само собой обращением по смещению.
    MIFы я пока готовить не умею, подключаю hex прямо в мегавизарде. Спасибо, буду пробовать.

    Thumb up 0 Thumb down 0

Написать комментарий

XHTML: Вы можете использовать эти теги: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

Bug Report
Локализовано: шаблоны Wordpress