-
Notifications
You must be signed in to change notification settings - Fork 3
/
mdv.v
185 lines (153 loc) · 5.78 KB
/
mdv.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
//
// mdv.v - Microdrive
//
// Sinclair QL for the MiST
// https://github.com/mist-devel
//
// Copyright (c) 2015 Till Harbaum <till@harbaum.org>
//
// This source file is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published
// by the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This source file is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
module mdv (
input clk, // 21mhz clock
input reset,
input mdv_drive,
input sel, // select microdrive 1 or 2
// control bits
output gap,
output tx_empty,
output rx_ready,
output [7:0] dout,
// ram interface to read image
input download,
input [24:0] dl_addr,
input mem_ena,
input mem_cycle,
input mem_clk,
output reg mem_read,
output reg [24:0] mem_addr,
input [15:0] mem_din
);
// mdv1_ image stored at h800000, mdv2_ image stored at address h900000
wire [24:0] BASE_ADDR = (mdv_drive == 1)?25'h800000:25'h900000;
// a gap is permanently present if no mdv is inserted or if
// there's a gap on the inserted one. This is the signal that triggers
// the irq and can be seen by the cpu
assign gap = (!mdv_present) || mdv_gap /* synthesis keep */;
// the mdv_rx_ready flag must be quite short as the CPU never waist for it to end
wire mdv_valid = (mdv_bit_cnt[2:0] == 2);
assign rx_ready = mdv_present && mdv_data_valid && mdv_valid /* synthesis keep */;
assign tx_empty = 1'b0;
// microdrive implementation works with images which are uploaded by the user into
// the part of ram which is unavailable to the 68k CPU (>16MB). It is then continously
// replayed from there at 200kbit/s
reg [24:0] mdv_end /* synthesis noprune */;
// determine mdv image size after download
always @(negedge download or posedge reset) begin
if(reset) mdv_end <= BASE_ADDR;
else mdv_end <= dl_addr;
end
// the microdrive at 200kbit/s reads a bit every 8.3us and needs a new word
// every 80us. video hsync comes every 64us. A new word can thus be read in
// the hsync phase while video isn't accessing ram and the next word will not
// be needed before the next hsync
// gaps are 2800/3400 us which is 35 words at 200kbit/s
assign dout = mdv_bit_cnt[3]?mdv_data[7:0]:mdv_data[15:8];
// data is valid at the end of the video cycle while mem_read is active
reg [15:0] mdv_din /* synthesis noprune */;
always @(negedge mem_cycle)
if(mem_read) mdv_din <= mem_din;
// activate memory read for the next full video cycle after mdv_required
always @(negedge mem_clk) begin
// mdv memory enable signal from zx8301 to give mdv emulation ram access
if(!mem_cycle)
mem_read <= mdv_rd_wait && mem_ena;
end
// wait for next hsync to service request
reg mdv_rd_wait /* synthesis noprune */;
wire mdv_rd_ack = mem_read;
always @(posedge mdv_next_word or posedge mdv_rd_ack) begin
if(mdv_rd_ack) mdv_rd_wait <= 1'b0;
else mdv_rd_wait <= 1'b1;
end
// a microdrive image is present if at least one word is in the buffer
wire mdv_present = sel && (mdv_end != BASE_ADDR);
reg mdv_next_word /* synthesis noprune */;
reg [3:0] mdv_bit_cnt /* synthesis noprune */;
// also generate gap timing
reg [9:0] mdv_gap_cnt /* synthesis noprune */;
reg mdv_gap_state /* synthesis noprune */;
reg mdv_gap_active /* synthesis noprune */;
reg [15:0] mdv_data;
reg mdv_data_valid;
reg mdv_gap;
always @(posedge mdv_clk) begin
mdv_next_word <= 1'b0;
mdv_bit_cnt <= mdv_bit_cnt + 4'd1;
if(mdv_bit_cnt == 15) begin
mdv_data <= mdv_din;
mdv_data_valid <= !mdv_gap_active &&
// don't generate data_valid for first 12 bytes (preamble)
(mdv_gap_cnt > 5) &&
// and also not for the sector internal preamble
!(mdv_gap_state && (mdv_gap_cnt > 7) && (mdv_gap_cnt < 12));
mdv_next_word <= 1'b1;
// reset counters when address is out of range
if((mem_addr > mdv_end)||(mem_addr < BASE_ADDR)) begin
mem_addr <= BASE_ADDR;
// assume we start at the end of a post-sector/pre-header gap
mdv_gap_cnt <= 10'd0; // count bytes until gap
mdv_gap_state <= 1'b1; // toggle header + data gap
mdv_gap_active <= 1'b1; // gap atm
mdv_gap <= 1'b1;
end else begin
mdv_gap_cnt <= mdv_gap_cnt + 10'd1;
if(mdv_gap_active) begin
// stop sending gap after 35 words = 70 bytes = 2800us
if(mdv_gap_cnt == 34) begin
mdv_gap_cnt <= 10'd0; // restart counter until next gap
mdv_gap_active <= 1'b0; // no gap anymore
mdv_gap_state <= !mdv_gap_state; // toggle gap/data
mdv_gap <= 1'b0;
end
end else begin
mem_addr <= mem_addr + 25'd1;
if((!mdv_gap_state) && (mdv_gap_cnt == 13)) begin
// done reading 14 words header data
mdv_gap_cnt <= 10'd0; // restart counter for gap
mdv_gap_active <= 1'b1; // now comes a gap
mdv_gap <= 1'b1;
end else if(mdv_gap_state && (mdv_gap_cnt == 328)) begin
// done reading 330 words sector data
mdv_gap_cnt <= 10'd0; // restart counter for gap
mdv_gap_active <= 1'b1; // now comes a gap
mdv_gap <= 1'b1;
end
end
end
end
end
// microdrive clock runs at 200khz
// -> new word required every 80us
localparam mdv_clk_scaler = 21000000/(2*200000)-1;
reg mdv_clk;
reg [7:0] mdv_clk_cnt;
always @(posedge clk) begin
if(mdv_clk_cnt == mdv_clk_scaler) begin
mdv_clk_cnt <= 8'd0;
mdv_clk <= !mdv_clk;
end else
mdv_clk_cnt <= mdv_clk_cnt + 8'd1;
end
endmodule