-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathMC6845.v
220 lines (200 loc) · 7.18 KB
/
MC6845.v
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
`timescale 1 ns/10 ps
// http://bitsavers.trailing-edge.com/components/motorola/_dataSheets/6845.pdf
module MC6845(E, CSn, RS, RW, D,
CLK, RSTn,
HSYNC, VSYNC, DE);
input CSn; // chip enable. active low -> writing or reading from register.
input E; // enable/clock, neg edge -> enables data IO buffers and clocks data
inout [7:0] D; // biderectional data bus
input RS; // register select. low -> writing to address register, high -> writing to register selected by address
input RW; // read or write register. low -> write, high -> read
/* MDA notes
RW is driven directly by ~IOW
*/
input CLK; // character clock, neg edge -> active edge.
input RSTn; // reset. active low -> all registers cleared and outputs are driven low.
output HSYNC; //
output VSYNC;
output DE; // display enable. high -> addressing in active display area.
// registers
/* notes
minimized size of R3 to necessary 4 bits
minimized size of R8 to necessary 2 bits
R12 and R13 were merged together
R14 and R15 were merged
R16 and R17 were merged
TODO: do I want to implement LIGHT_PEN?
*/
reg [4:0] ADDRESS; // AR XXXXX address. selects which register to read or write from. write-only
reg [7:0] HORIZONTAL_TOTAL = 8'h5e; // R0 00000 horizontal timing. determines HS frequency by defining HS period in character times. (DISPLAYED CHARS + NON_DISPLAYED - 1 character times) write-only
reg [7:0] HORIZONTAL_DISPLAYED = 8'h4c; // R1 00001 displayed horizontal characeters per line. R1 > R0. write-only
reg [7:0] H_SYNC_POS = 8'h4e; // R2 00010 horizontal sync position. R2 + R3 > R0. R2 > R1. write-only
reg [3:0] SYNC_WIDTH = 8'h0c; // R3 00011 sync width for HS. VS sync width is fixed to 16. HS pulse width is in character times. write-only
reg [6:0] VERTICAL_TOTAL = 8'h40; // R4 00100 vertical timing. determines VS frequency. in character row times - 1.
reg [4:0] VERTICAL_TOTAL_ADJ = 8'h05; // R5 00101 fraction component of vertical timing.
reg [6:0] VERTICAL_DISPLAYED = 8'h3c; // R6 00110 number of displayed vertical character rows. in character row times. R6 < R4.
reg [6:0] V_SYNC_POS = 8'h3d; // R7 00111 vertical sync position. in character row times R6 <= R7 <= R4.
reg [1:0] INTERLACE_MODE_SKEW; // R8 01000
reg [4:0] MAX_SCANLINE_ADDRESS = 8'h07; // R9 01001 number of scan lines per character including spacing. value is no scanlines - 1
reg [6:0] CURSOR_START; // R10 01010
reg [4:0] CURSOR_END; // R11 01011
reg [13:0] START_ADDRESS; // R12 01100 high
// R13 01101 low
reg [13:0] CURSOR; // R14 01110 high
// R15 01111 low
reg [13:0] LIGHT_PEN; // R16 10000 high
// R17 10001 low
reg [7:0] READ_BUFFER;
always @(negedge E, negedge RSTn)
begin
if (!RSTn) begin // reset processor interface
ADDRESS <= 5'b0;
// TODO: pull D low?
READ_BUFFER <= 8'b0;
end
else begin
if (!CSn) begin
if(!RS) begin
if (!RW) ADDRESS <= D[4:0];
end
else begin
case (ADDRESS)
5'b00000 : HORIZONTAL_TOTAL <= D;
5'b00001 : HORIZONTAL_DISPLAYED <= D;
5'b00010 : H_SYNC_POS <= D;
5'b00011 : SYNC_WIDTH <= D[3:0];
5'b00100 : VERTICAL_TOTAL <= D[6:0];
5'b00101 : VERTICAL_TOTAL_ADJ <= D[4:0];
5'b00110 : VERTICAL_DISPLAYED <= D[6:0];
5'b00111 : V_SYNC_POS <= D[6:0];
5'b01000 : INTERLACE_MODE_SKEW <= D[1:0];
5'b01001 : MAX_SCANLINE_ADDRESS <= D[4:0];
5'b01010 : CURSOR_START <= D[6:0];
5'b01011 : CURSOR_END <= D[4:0];
5'b01100 : START_ADDRESS[13:8] <= D[5:0];
5'b01101 : START_ADDRESS[7:0] <= D[7:0];
5'b01110 : begin
if(!RW)
CURSOR[13:8] <= D[5:0];
else
READ_BUFFER[7:6] <= 2'b0;
READ_BUFFER[5:0] <= CURSOR[13:8]; // TODO set top 2 bits to 0
end
5'b01111 : begin
if(!RW)
CURSOR[7:0] <= D[7:0];
else
READ_BUFFER[7:0] <= CURSOR[7:0];
end
5'b10000 : begin
if(!RW)
LIGHT_PEN[13:8] <= D[5:0];
else
READ_BUFFER[7:6] <= 2'b0;
READ_BUFFER[5:0] <= LIGHT_PEN[13:8];
end
5'b10001 : begin
if(!RW)
LIGHT_PEN[7:0] <= D[7:0];
else
READ_BUFFER[7:0] <= LIGHT_PEN[7:0];
end
endcase
end
end
end
end
reg [7:0] H_CTR = 0; // horizontal dot position
reg H_DISP = 0; // 1 if in horizontal displayed region
reg [3:0] HSYNC_COUNTER = 0; // counter for HSYNC pulse width;
reg IN_HSYNC = 0; // this is only 1 during HSYNC pulse
//time sync_start;
//time disp_start;
//time disp_end;
//time line_start;
reg [6:0] LINE_CTR = 0;
reg V_DISP = 0; // in vertical displayed region
reg [4:0] SCAN_LINE_CTR = 0;
reg [3:0] VSYNC_COUNTER = 0; // vsync width
reg IN_VSYNC = 0;
always @(negedge CLK)
begin
//$strobe("H_CTR %x V_CTR %x at %d micros", H_CTR, LINE_CTR, $time/1000);
// HSYNC generation
H_CTR = H_CTR + 1;
case (H_CTR)
HORIZONTAL_TOTAL + 1 : begin // we reached end of horizontal line
H_CTR = 8'b0;
H_DISP <= 1;
vertical_timing();
//$display("started display region");
//disp_start = $time;
end
HORIZONTAL_DISPLAYED : begin
H_DISP <= 0;
IN_HSYNC <= 0;
//$display("display region lasted %d micros", ($time - disp_start)/1000);
// TODO: memory refresh stuff needs to be done since we want to show characters until this point
end
H_SYNC_POS : begin
IN_HSYNC <= 1;
//sync_start = $time;
end
8'd256 : H_CTR = 0;
endcase
if(IN_HSYNC) // enable HSYNC pulse width counter
HSYNC_COUNTER <= HSYNC_COUNTER + 1;
case (HSYNC_COUNTER)
SYNC_WIDTH: begin
IN_HSYNC <= 0;
HSYNC_COUNTER <= 0;
// TODO: this is somehow involved in vertical control
//$strobe("length of sync pulse: %d micros", ($time - sync_start)/1000);
end
4'b1111 : HSYNC_COUNTER <= 0;
endcase
end
task vertical_timing;
begin
if(LINE_CTR == VERTICAL_TOTAL) begin
LINE_CTR = 0;
V_DISP = 1'b1;
end else begin
if(SCAN_LINE_CTR != MAX_SCANLINE_ADDRESS) begin // also check against vertical adjust register
SCAN_LINE_CTR = SCAN_LINE_CTR + 1;
end else begin
SCAN_LINE_CTR = 0;
LINE_CTR = LINE_CTR + 1; // increment number of lines
//$strobe("line ctr %x", LINE_CTR);
case (LINE_CTR)
VERTICAL_DISPLAYED + 1: begin
V_DISP = 0; // we entered the blanking region
//$display("vertical display region lasted %d ms scan_line_ctr %x, h_ctr %x",
// ($time - disp_start)/1000000, SCAN_LINE_CTR, H_CTR);
//disp_end = $time;
end
V_SYNC_POS + 1: begin
IN_VSYNC = 1;
//$strobe("front porch lasted %d micros started vsync at %x", ($time - disp_end)/1000, LINE_CTR);
//sync_start = $time;
end
endcase
end
end
if(IN_VSYNC) begin
if(VSYNC_COUNTER == 5'h2) begin
VSYNC_COUNTER = 0;
IN_VSYNC = 0;
//$strobe("vsync lasted %d micros", ($time - sync_start)/1000);
end else begin
VSYNC_COUNTER = VSYNC_COUNTER + 1;
//$strobe("vsync_counter %x", VSYNC_COUNTER);
end
end
end
endtask
assign HSYNC = IN_HSYNC;
assign VSYNC = IN_VSYNC;
assign DE = H_DISP & V_DISP;
assign D[7:0] = RW ? READ_BUFFER : 8'bz;
endmodule