1 |
2 |
dgisselq |
////////////////////////////////////////////////////////////////////////////////
|
2 |
|
|
//
|
3 |
|
|
// Filename: wbucompress.v
|
4 |
|
|
//
|
5 |
|
|
// Project: XuLA2 board
|
6 |
|
|
//
|
7 |
|
|
// Purpose: When reading many words that are identical, it makes no sense
|
8 |
|
|
// to spend the time transmitting the same thing over and over
|
9 |
|
|
// again, especially on a slow channel. Hence this routine uses a table
|
10 |
|
|
// lookup to see if the word to be transmitted was one from the recent
|
11 |
|
|
// past. If so, the word is replaced with an address of the recently
|
12 |
|
|
// transmitted word. Mind you, the table lookup takes one clock per table
|
13 |
|
|
// entry, so even if a word is in the table it might not be found in time.
|
14 |
|
|
// If the word is not in the table, or if it isn't found due to a lack of
|
15 |
|
|
// time, the word is placed into the table while incrementing every other
|
16 |
|
|
// table address.
|
17 |
|
|
//
|
18 |
|
|
// Oh, and on a new address--the table is reset and starts over. This way,
|
19 |
|
|
// any time the host software changes, the host software will always start
|
20 |
|
|
// by issuing a new address--hence the table is reset for every new piece
|
21 |
|
|
// of software that may wish to communicate.
|
22 |
|
|
//
|
23 |
|
|
//
|
24 |
|
|
// Creator: Dan Gisselquist, Ph.D.
|
25 |
|
|
// Gisselquist Technology, LLC
|
26 |
|
|
//
|
27 |
|
|
////////////////////////////////////////////////////////////////////////////////
|
28 |
|
|
//
|
29 |
|
|
// Copyright (C) 2015, Gisselquist Technology, LLC
|
30 |
|
|
//
|
31 |
|
|
// This program is free software (firmware): you can redistribute it and/or
|
32 |
|
|
// modify it under the terms of the GNU General Public License as published
|
33 |
|
|
// by the Free Software Foundation, either version 3 of the License, or (at
|
34 |
|
|
// your option) any later version.
|
35 |
|
|
//
|
36 |
|
|
// This program is distributed in the hope that it will be useful, but WITHOUT
|
37 |
|
|
// ANY WARRANTY; without even the implied warranty of MERCHANTIBILITY or
|
38 |
|
|
// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
39 |
|
|
// for more details.
|
40 |
|
|
//
|
41 |
|
|
// License: GPL, v3, as defined and found on www.gnu.org,
|
42 |
|
|
// http://www.gnu.org/licenses/gpl.html
|
43 |
|
|
//
|
44 |
|
|
//
|
45 |
|
|
////////////////////////////////////////////////////////////////////////////////
|
46 |
|
|
//
|
47 |
|
|
//
|
48 |
|
|
// All input words are valid codewords. If we can, we make them
|
49 |
|
|
// better here.
|
50 |
|
|
module wbucompress(i_clk, i_stb, i_codword, o_stb, o_cword, i_busy);
|
51 |
|
|
parameter DW=32, CW=36, TBITS=10;
|
52 |
|
|
input i_clk, i_stb;
|
53 |
|
|
input [(CW-1):0] i_codword;
|
54 |
|
|
output wire o_stb;
|
55 |
|
|
output wire [(CW-1):0] o_cword;
|
56 |
|
|
input i_busy;
|
57 |
|
|
|
58 |
|
|
//
|
59 |
|
|
//
|
60 |
|
|
// First stage is to compress the address.
|
61 |
|
|
// This stage requires one clock.
|
62 |
|
|
//
|
63 |
|
|
// ISTB,ICODWORD
|
64 |
|
|
// ISTB2,IWRD2 ASTB,AWORD
|
65 |
|
|
// ISTB3,IWRD3 ASTB2,AWRD2 I_BUSY(1)
|
66 |
|
|
// ISTB3,IWRD3 ASTB2,AWRD2 I_BUSY(1)
|
67 |
|
|
// ISTB3,IWRD3 ASTB2,AWRD2 I_BUSY(1)
|
68 |
|
|
// ISTB3,IWRD3 ASTB2,AWRD2
|
69 |
|
|
// ISTB4,IWRD4 ASTB3,AWRD3 I_BUSY(2)
|
70 |
|
|
// ISTB4,IWRD4 ASTB3,AWRD3 I_BUSY(2)
|
71 |
|
|
// ISTB4,IWRD4 ASTB3,AWRD3 I_BUSY(2)
|
72 |
|
|
reg a_stb;
|
73 |
|
|
reg [35:0] a_addrword;
|
74 |
|
|
wire [31:0] w_addr;
|
75 |
|
|
assign w_addr = i_codword[31:0];
|
76 |
|
|
always @(posedge i_clk)
|
77 |
|
|
if ((i_stb)&&(~a_stb))
|
78 |
|
|
begin
|
79 |
|
|
if (i_codword[35:32] != 4'h2)
|
80 |
|
|
begin
|
81 |
|
|
a_addrword <= i_codword;
|
82 |
|
|
end else if (w_addr[31:6] == 26'h00)
|
83 |
|
|
a_addrword <= { 6'hc, w_addr[ 5:0], 24'h00 };
|
84 |
|
|
else if (w_addr[31:12] == 20'h00)
|
85 |
|
|
a_addrword <= { 6'hd, w_addr[11:0], 18'h00 };
|
86 |
|
|
else if (w_addr[31:18] == 14'h00)
|
87 |
|
|
a_addrword <= { 6'he, w_addr[17:0], 12'h00 };
|
88 |
|
|
else if (w_addr[31:24] == 8'h00)
|
89 |
|
|
a_addrword <= { 6'hf, w_addr[23:0], 6'h00 };
|
90 |
|
|
else begin
|
91 |
|
|
a_addrword <= i_codword;
|
92 |
|
|
end
|
93 |
|
|
end
|
94 |
|
|
initial a_stb = 1'b0;
|
95 |
|
|
always @(posedge i_clk)
|
96 |
|
|
if ((i_stb)&&(~a_stb))
|
97 |
|
|
a_stb <= i_stb;
|
98 |
|
|
else if (~i_busy)
|
99 |
|
|
a_stb <= 1'b0;
|
100 |
|
|
|
101 |
|
|
|
102 |
|
|
//
|
103 |
|
|
//
|
104 |
|
|
// The next stage attempts to replace data codewords with previous
|
105 |
|
|
// codewords that may have been sent. The memory is only allowed
|
106 |
|
|
// to be as old as the last new address command. In this fashion,
|
107 |
|
|
// any program that wishes to talk to the device can start with a
|
108 |
|
|
// known compression table by simply setting the address and then
|
109 |
|
|
// reading from the device.
|
110 |
|
|
//
|
111 |
|
|
|
112 |
|
|
// We start over any time a new value shows up, and
|
113 |
|
|
// the follow-on isn't busy and can take it. Likewise,
|
114 |
|
|
// we reset the writer on the compression any time a
|
115 |
|
|
// i_clr value comes through (i.e., ~i_cyc or new
|
116 |
|
|
// address)
|
117 |
|
|
|
118 |
|
|
wire w_accepted;
|
119 |
|
|
assign w_accepted = (a_stb)&&(~i_busy);
|
120 |
|
|
|
121 |
|
|
reg r_stb;
|
122 |
|
|
always @(posedge i_clk)
|
123 |
|
|
r_stb <= a_stb;
|
124 |
|
|
/*
|
125 |
|
|
reg [35:0] r_word;
|
126 |
|
|
always @(posedge i_clk)
|
127 |
|
|
if (a_stb)
|
128 |
|
|
r_word <= a_addrword;
|
129 |
|
|
*/
|
130 |
|
|
wire [35:0] r_word;
|
131 |
|
|
assign r_word = a_addrword;
|
132 |
|
|
|
133 |
|
|
|
134 |
|
|
//
|
135 |
|
|
// First step of the compression is keeping track of a compression
|
136 |
|
|
// table. And the first part of that is keeping track of what address
|
137 |
|
|
// to write into the compression table, and whether or not the entire
|
138 |
|
|
// table is full or not. This logic follows:
|
139 |
|
|
//
|
140 |
|
|
reg [(TBITS-1):0] tbl_addr;
|
141 |
|
|
reg r_compressed, tbl_filled;
|
142 |
|
|
// First part, write the compression table
|
143 |
|
|
always @(posedge i_clk)
|
144 |
|
|
// If we send a new address, then reset the table to empty
|
145 |
|
|
if (w_accepted)
|
146 |
|
|
begin
|
147 |
|
|
// Reset on new address (0010xx) and on new compressed
|
148 |
|
|
// addresses (0011ll).
|
149 |
|
|
if (o_cword[35:33]==3'h1)
|
150 |
|
|
tbl_addr <= 0;
|
151 |
|
|
// Otherwise, on any valid return result that wasn't
|
152 |
|
|
// from our table, for whatever reason (such as didn't
|
153 |
|
|
// have the clocks to find it, etc.), increment the
|
154 |
|
|
// address to add another value into our table
|
155 |
|
|
else if (o_cword[35:33] == 3'b111)
|
156 |
|
|
tbl_addr <= tbl_addr + {{(TBITS-1){1'b0}},1'b1};
|
157 |
|
|
end
|
158 |
|
|
always @(posedge i_clk)
|
159 |
|
|
if ((w_accepted)&&(o_cword[35:33]==3'h1)) // on new address
|
160 |
|
|
tbl_filled <= 1'b0;
|
161 |
|
|
else if (tbl_addr == 10'h3ff)
|
162 |
|
|
tbl_filled <= 1'b1;
|
163 |
|
|
|
164 |
|
|
// Now that we know where we are writing into the table, and what
|
165 |
|
|
// values of the table are valid, we need to actually write into
|
166 |
|
|
// the table.
|
167 |
|
|
//
|
168 |
|
|
// We can keep this logic really simple by writing on every clock
|
169 |
|
|
// and writing junk on many of those clocks, but we'll need to remember
|
170 |
|
|
// that the value of the table at tbl_addr is unreliable until tbl_addr
|
171 |
|
|
// changes.
|
172 |
|
|
//
|
173 |
|
|
reg [31:0] compression_tbl [0:((1<<TBITS)-1)];
|
174 |
|
|
// Write new values into the table
|
175 |
|
|
always @(posedge i_clk)
|
176 |
|
|
compression_tbl[tbl_addr] <= { r_word[32:31], r_word[29:0] };
|
177 |
|
|
|
178 |
|
|
// Now that we have a working table, can we use it?
|
179 |
|
|
// On any new word, we'll start looking through our codewords.
|
180 |
|
|
// If we find any that matches, we're there. We might (or might not)
|
181 |
|
|
// make it through the table first. That's irrelevant. We just look
|
182 |
|
|
// while we can.
|
183 |
|
|
reg [(TBITS-1):0] rd_addr;
|
184 |
|
|
wire [(TBITS-1):0] nxt_rd_addr;
|
185 |
|
|
assign nxt_rd_addr = rd_addr - { {(TBITS-1){1'b0}}, 1'b1 };
|
186 |
|
|
initial rd_addr = 0;
|
187 |
|
|
always @(posedge i_clk)
|
188 |
|
|
if ((w_accepted)||(~a_stb))
|
189 |
|
|
// Keep in mind, if a write was just accepted, then
|
190 |
|
|
// rd_addr will need to be reset on the next clock
|
191 |
|
|
// when (~a_stb). Hence this must be a two clock
|
192 |
|
|
// update
|
193 |
|
|
rd_addr <= tbl_addr + {(TBITS){1'b1}};
|
194 |
|
|
else if ((nxt_rd_addr != tbl_addr)&&(~match)
|
195 |
|
|
&&((~nxt_rd_addr[TBITS-1])||(tbl_filled)))
|
196 |
|
|
rd_addr <= nxt_rd_addr;
|
197 |
|
|
|
198 |
|
|
reg [(DW-1):0] cword;
|
199 |
|
|
reg [(TBITS-1):0] caddr;
|
200 |
|
|
always @(posedge i_clk)
|
201 |
|
|
begin
|
202 |
|
|
cword <= compression_tbl[rd_addr];
|
203 |
|
|
caddr <= rd_addr;
|
204 |
|
|
end
|
205 |
|
|
|
206 |
|
|
reg match;
|
207 |
|
|
reg [9:0] matchaddr;
|
208 |
|
|
always @(posedge i_clk)
|
209 |
|
|
if((w_accepted)||(~a_stb)||(~r_stb))// Reset upon any write
|
210 |
|
|
match <= 1'b0;
|
211 |
|
|
else if (~match)
|
212 |
|
|
begin
|
213 |
|
|
// To be a match, the table must not be empty,
|
214 |
|
|
match <= (({1'b0, caddr } < {tbl_filled, tbl_addr}))
|
215 |
|
|
// the word we are matching to must be
|
216 |
|
|
// in the form of a read command
|
217 |
|
|
&&(r_word[35:33] == 3'b111)
|
218 |
|
|
// And the word in the table must be
|
219 |
|
|
// identical to the word we are about
|
220 |
|
|
// to send.
|
221 |
|
|
&&(cword == { r_word[32:31], r_word[29:0] });
|
222 |
|
|
matchaddr <= tbl_addr-caddr;
|
223 |
|
|
end
|
224 |
|
|
|
225 |
|
|
// Did we find something?
|
226 |
|
|
wire [(TBITS-1):0] adr_diff;
|
227 |
|
|
wire [9:0] adr_dbld;
|
228 |
|
|
wire [2:0] adr_hlfd;
|
229 |
|
|
assign adr_diff = matchaddr;
|
230 |
|
|
assign adr_hlfd = matchaddr[2:0]- 3'd2;
|
231 |
|
|
assign adr_dbld = matchaddr- 10'd10;
|
232 |
|
|
initial r_compressed = 1'b0;
|
233 |
|
|
reg [(CW-1):0] r_cword; // Record our result
|
234 |
|
|
always @(posedge i_clk)
|
235 |
|
|
begin
|
236 |
|
|
if ((~a_stb)||(~r_stb)||(w_accepted))//Reset whenever word gets written
|
237 |
|
|
r_compressed <= 1'b0; // to our output
|
238 |
|
|
else if (r_compressed)//Already compressed, wait 'til sent
|
239 |
|
|
;
|
240 |
|
|
else if ((match)&&(matchaddr < 10'd521)&&(r_word == a_addrword))
|
241 |
|
|
begin
|
242 |
|
|
if (matchaddr == 10'h1)
|
243 |
|
|
r_cword[35:30] <= { 5'h3, r_word[30] };
|
244 |
|
|
else if (adr_diff < 10'd10)
|
245 |
|
|
r_cword[35:30] <= { 2'b10, adr_hlfd, r_word[30] };
|
246 |
|
|
else // if (adr_diff < 10'd521)
|
247 |
|
|
r_cword[35:24] <= { 2'b01, adr_dbld[8:6],
|
248 |
|
|
r_word[30], adr_dbld[5:0] };
|
249 |
|
|
r_compressed <= 1'b1;
|
250 |
|
|
end else
|
251 |
|
|
r_cword <= r_word;
|
252 |
|
|
end
|
253 |
|
|
|
254 |
|
|
// Can we do this without a clock delay?
|
255 |
|
|
assign o_stb = a_stb;
|
256 |
|
|
assign o_cword = (r_compressed)?(r_cword):(a_addrword);
|
257 |
|
|
endmodule
|
258 |
|
|
|