├── .gitignore
├── LICENSE
├── README.md
├── bench
├── formal
│ ├── .gitignore
│ ├── Makefile
│ ├── faxivideo.v
│ ├── qoi_compress.sby
│ └── qoi_encoder.sby
├── sim
│ ├── .gitignore
│ ├── Makefile
│ └── main_tb.cpp
└── verilog
│ ├── Makefile
│ └── tb_top.v
├── doc
├── devstat.dia
└── devstat.png
└── rtl
├── qoi_compress.v
├── qoi_decoder.v
├── qoi_decompress.v
├── qoi_encoder.v
├── qoi_recorder.v
└── qoi_skid.v
/.gitignore:
--------------------------------------------------------------------------------
1 | legal.txt
2 | .svn
3 | xilinx
4 | obj_dir
5 | obj-pc
6 | obj-zip
7 | *.o
8 | *.a
9 | *.vcd
10 | *.fst
11 | *.fst.hier
12 | .swp
13 | .*.swp
14 | .*.swo
15 | svn-commit*
16 | *_tb
17 | *_tb.dbl
18 | *dbg.txt
19 | *dump.txt
20 | *debug.txt
21 | tags
22 | cpudefs.h
23 | design.h
24 | octave-workspace
25 | core
26 | *.aux
27 | *.log
28 | *.out
29 | *.ps
30 | *.vrb
31 | *.yslog
32 | *.smt2
33 | vzip
34 | thruhull
35 | legal.txt
36 | vivado*.jou
37 | vivado*.log
38 | vivado*.str
39 | vivado*.debug
40 | xtim-*.txt
41 | /20*-build.v
42 | /refs/
43 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | GNU GENERAL PUBLIC LICENSE
2 | Version 3, 29 June 2007
3 |
4 | Copyright (C) 2007 Free Software Foundation, Inc.
5 | Everyone is permitted to copy and distribute verbatim copies
6 | of this license document, but changing it is not allowed.
7 |
8 | Preamble
9 |
10 | The GNU General Public License is a free, copyleft license for
11 | software and other kinds of works.
12 |
13 | The licenses for most software and other practical works are designed
14 | to take away your freedom to share and change the works. By contrast,
15 | the GNU General Public License is intended to guarantee your freedom to
16 | share and change all versions of a program--to make sure it remains free
17 | software for all its users. We, the Free Software Foundation, use the
18 | GNU General Public License for most of our software; it applies also to
19 | any other work released this way by its authors. You can apply it to
20 | your programs, too.
21 |
22 | When we speak of free software, we are referring to freedom, not
23 | price. Our General Public Licenses are designed to make sure that you
24 | have the freedom to distribute copies of free software (and charge for
25 | them if you wish), that you receive source code or can get it if you
26 | want it, that you can change the software or use pieces of it in new
27 | free programs, and that you know you can do these things.
28 |
29 | To protect your rights, we need to prevent others from denying you
30 | these rights or asking you to surrender the rights. Therefore, you have
31 | certain responsibilities if you distribute copies of the software, or if
32 | you modify it: responsibilities to respect the freedom of others.
33 |
34 | For example, if you distribute copies of such a program, whether
35 | gratis or for a fee, you must pass on to the recipients the same
36 | freedoms that you received. You must make sure that they, too, receive
37 | or can get the source code. And you must show them these terms so they
38 | know their rights.
39 |
40 | Developers that use the GNU GPL protect your rights with two steps:
41 | (1) assert copyright on the software, and (2) offer you this License
42 | giving you legal permission to copy, distribute and/or modify it.
43 |
44 | For the developers' and authors' protection, the GPL clearly explains
45 | that there is no warranty for this free software. For both users' and
46 | authors' sake, the GPL requires that modified versions be marked as
47 | changed, so that their problems will not be attributed erroneously to
48 | authors of previous versions.
49 |
50 | Some devices are designed to deny users access to install or run
51 | modified versions of the software inside them, although the manufacturer
52 | can do so. This is fundamentally incompatible with the aim of
53 | protecting users' freedom to change the software. The systematic
54 | pattern of such abuse occurs in the area of products for individuals to
55 | use, which is precisely where it is most unacceptable. Therefore, we
56 | have designed this version of the GPL to prohibit the practice for those
57 | products. If such problems arise substantially in other domains, we
58 | stand ready to extend this provision to those domains in future versions
59 | of the GPL, as needed to protect the freedom of users.
60 |
61 | Finally, every program is threatened constantly by software patents.
62 | States should not allow patents to restrict development and use of
63 | software on general-purpose computers, but in those that do, we wish to
64 | avoid the special danger that patents applied to a free program could
65 | make it effectively proprietary. To prevent this, the GPL assures that
66 | patents cannot be used to render the program non-free.
67 |
68 | The precise terms and conditions for copying, distribution and
69 | modification follow.
70 |
71 | TERMS AND CONDITIONS
72 |
73 | 0. Definitions.
74 |
75 | "This License" refers to version 3 of the GNU General Public License.
76 |
77 | "Copyright" also means copyright-like laws that apply to other kinds of
78 | works, such as semiconductor masks.
79 |
80 | "The Program" refers to any copyrightable work licensed under this
81 | License. Each licensee is addressed as "you". "Licensees" and
82 | "recipients" may be individuals or organizations.
83 |
84 | To "modify" a work means to copy from or adapt all or part of the work
85 | in a fashion requiring copyright permission, other than the making of an
86 | exact copy. The resulting work is called a "modified version" of the
87 | earlier work or a work "based on" the earlier work.
88 |
89 | A "covered work" means either the unmodified Program or a work based
90 | on the Program.
91 |
92 | To "propagate" a work means to do anything with it that, without
93 | permission, would make you directly or secondarily liable for
94 | infringement under applicable copyright law, except executing it on a
95 | computer or modifying a private copy. Propagation includes copying,
96 | distribution (with or without modification), making available to the
97 | public, and in some countries other activities as well.
98 |
99 | To "convey" a work means any kind of propagation that enables other
100 | parties to make or receive copies. Mere interaction with a user through
101 | a computer network, with no transfer of a copy, is not conveying.
102 |
103 | An interactive user interface displays "Appropriate Legal Notices"
104 | to the extent that it includes a convenient and prominently visible
105 | feature that (1) displays an appropriate copyright notice, and (2)
106 | tells the user that there is no warranty for the work (except to the
107 | extent that warranties are provided), that licensees may convey the
108 | work under this License, and how to view a copy of this License. If
109 | the interface presents a list of user commands or options, such as a
110 | menu, a prominent item in the list meets this criterion.
111 |
112 | 1. Source Code.
113 |
114 | The "source code" for a work means the preferred form of the work
115 | for making modifications to it. "Object code" means any non-source
116 | form of a work.
117 |
118 | A "Standard Interface" means an interface that either is an official
119 | standard defined by a recognized standards body, or, in the case of
120 | interfaces specified for a particular programming language, one that
121 | is widely used among developers working in that language.
122 |
123 | The "System Libraries" of an executable work include anything, other
124 | than the work as a whole, that (a) is included in the normal form of
125 | packaging a Major Component, but which is not part of that Major
126 | Component, and (b) serves only to enable use of the work with that
127 | Major Component, or to implement a Standard Interface for which an
128 | implementation is available to the public in source code form. A
129 | "Major Component", in this context, means a major essential component
130 | (kernel, window system, and so on) of the specific operating system
131 | (if any) on which the executable work runs, or a compiler used to
132 | produce the work, or an object code interpreter used to run it.
133 |
134 | The "Corresponding Source" for a work in object code form means all
135 | the source code needed to generate, install, and (for an executable
136 | work) run the object code and to modify the work, including scripts to
137 | control those activities. However, it does not include the work's
138 | System Libraries, or general-purpose tools or generally available free
139 | programs which are used unmodified in performing those activities but
140 | which are not part of the work. For example, Corresponding Source
141 | includes interface definition files associated with source files for
142 | the work, and the source code for shared libraries and dynamically
143 | linked subprograms that the work is specifically designed to require,
144 | such as by intimate data communication or control flow between those
145 | subprograms and other parts of the work.
146 |
147 | The Corresponding Source need not include anything that users
148 | can regenerate automatically from other parts of the Corresponding
149 | Source.
150 |
151 | The Corresponding Source for a work in source code form is that
152 | same work.
153 |
154 | 2. Basic Permissions.
155 |
156 | All rights granted under this License are granted for the term of
157 | copyright on the Program, and are irrevocable provided the stated
158 | conditions are met. This License explicitly affirms your unlimited
159 | permission to run the unmodified Program. The output from running a
160 | covered work is covered by this License only if the output, given its
161 | content, constitutes a covered work. This License acknowledges your
162 | rights of fair use or other equivalent, as provided by copyright law.
163 |
164 | You may make, run and propagate covered works that you do not
165 | convey, without conditions so long as your license otherwise remains
166 | in force. You may convey covered works to others for the sole purpose
167 | of having them make modifications exclusively for you, or provide you
168 | with facilities for running those works, provided that you comply with
169 | the terms of this License in conveying all material for which you do
170 | not control copyright. Those thus making or running the covered works
171 | for you must do so exclusively on your behalf, under your direction
172 | and control, on terms that prohibit them from making any copies of
173 | your copyrighted material outside their relationship with you.
174 |
175 | Conveying under any other circumstances is permitted solely under
176 | the conditions stated below. Sublicensing is not allowed; section 10
177 | makes it unnecessary.
178 |
179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
180 |
181 | No covered work shall be deemed part of an effective technological
182 | measure under any applicable law fulfilling obligations under article
183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or
184 | similar laws prohibiting or restricting circumvention of such
185 | measures.
186 |
187 | When you convey a covered work, you waive any legal power to forbid
188 | circumvention of technological measures to the extent such circumvention
189 | is effected by exercising rights under this License with respect to
190 | the covered work, and you disclaim any intention to limit operation or
191 | modification of the work as a means of enforcing, against the work's
192 | users, your or third parties' legal rights to forbid circumvention of
193 | technological measures.
194 |
195 | 4. Conveying Verbatim Copies.
196 |
197 | You may convey verbatim copies of the Program's source code as you
198 | receive it, in any medium, provided that you conspicuously and
199 | appropriately publish on each copy an appropriate copyright notice;
200 | keep intact all notices stating that this License and any
201 | non-permissive terms added in accord with section 7 apply to the code;
202 | keep intact all notices of the absence of any warranty; and give all
203 | recipients a copy of this License along with the Program.
204 |
205 | You may charge any price or no price for each copy that you convey,
206 | and you may offer support or warranty protection for a fee.
207 |
208 | 5. Conveying Modified Source Versions.
209 |
210 | You may convey a work based on the Program, or the modifications to
211 | produce it from the Program, in the form of source code under the
212 | terms of section 4, provided that you also meet all of these conditions:
213 |
214 | a) The work must carry prominent notices stating that you modified
215 | it, and giving a relevant date.
216 |
217 | b) The work must carry prominent notices stating that it is
218 | released under this License and any conditions added under section
219 | 7. This requirement modifies the requirement in section 4 to
220 | "keep intact all notices".
221 |
222 | c) You must license the entire work, as a whole, under this
223 | License to anyone who comes into possession of a copy. This
224 | License will therefore apply, along with any applicable section 7
225 | additional terms, to the whole of the work, and all its parts,
226 | regardless of how they are packaged. This License gives no
227 | permission to license the work in any other way, but it does not
228 | invalidate such permission if you have separately received it.
229 |
230 | d) If the work has interactive user interfaces, each must display
231 | Appropriate Legal Notices; however, if the Program has interactive
232 | interfaces that do not display Appropriate Legal Notices, your
233 | work need not make them do so.
234 |
235 | A compilation of a covered work with other separate and independent
236 | works, which are not by their nature extensions of the covered work,
237 | and which are not combined with it such as to form a larger program,
238 | in or on a volume of a storage or distribution medium, is called an
239 | "aggregate" if the compilation and its resulting copyright are not
240 | used to limit the access or legal rights of the compilation's users
241 | beyond what the individual works permit. Inclusion of a covered work
242 | in an aggregate does not cause this License to apply to the other
243 | parts of the aggregate.
244 |
245 | 6. Conveying Non-Source Forms.
246 |
247 | You may convey a covered work in object code form under the terms
248 | of sections 4 and 5, provided that you also convey the
249 | machine-readable Corresponding Source under the terms of this License,
250 | in one of these ways:
251 |
252 | a) Convey the object code in, or embodied in, a physical product
253 | (including a physical distribution medium), accompanied by the
254 | Corresponding Source fixed on a durable physical medium
255 | customarily used for software interchange.
256 |
257 | b) Convey the object code in, or embodied in, a physical product
258 | (including a physical distribution medium), accompanied by a
259 | written offer, valid for at least three years and valid for as
260 | long as you offer spare parts or customer support for that product
261 | model, to give anyone who possesses the object code either (1) a
262 | copy of the Corresponding Source for all the software in the
263 | product that is covered by this License, on a durable physical
264 | medium customarily used for software interchange, for a price no
265 | more than your reasonable cost of physically performing this
266 | conveying of source, or (2) access to copy the
267 | Corresponding Source from a network server at no charge.
268 |
269 | c) Convey individual copies of the object code with a copy of the
270 | written offer to provide the Corresponding Source. This
271 | alternative is allowed only occasionally and noncommercially, and
272 | only if you received the object code with such an offer, in accord
273 | with subsection 6b.
274 |
275 | d) Convey the object code by offering access from a designated
276 | place (gratis or for a charge), and offer equivalent access to the
277 | Corresponding Source in the same way through the same place at no
278 | further charge. You need not require recipients to copy the
279 | Corresponding Source along with the object code. If the place to
280 | copy the object code is a network server, the Corresponding Source
281 | may be on a different server (operated by you or a third party)
282 | that supports equivalent copying facilities, provided you maintain
283 | clear directions next to the object code saying where to find the
284 | Corresponding Source. Regardless of what server hosts the
285 | Corresponding Source, you remain obligated to ensure that it is
286 | available for as long as needed to satisfy these requirements.
287 |
288 | e) Convey the object code using peer-to-peer transmission, provided
289 | you inform other peers where the object code and Corresponding
290 | Source of the work are being offered to the general public at no
291 | charge under subsection 6d.
292 |
293 | A separable portion of the object code, whose source code is excluded
294 | from the Corresponding Source as a System Library, need not be
295 | included in conveying the object code work.
296 |
297 | A "User Product" is either (1) a "consumer product", which means any
298 | tangible personal property which is normally used for personal, family,
299 | or household purposes, or (2) anything designed or sold for incorporation
300 | into a dwelling. In determining whether a product is a consumer product,
301 | doubtful cases shall be resolved in favor of coverage. For a particular
302 | product received by a particular user, "normally used" refers to a
303 | typical or common use of that class of product, regardless of the status
304 | of the particular user or of the way in which the particular user
305 | actually uses, or expects or is expected to use, the product. A product
306 | is a consumer product regardless of whether the product has substantial
307 | commercial, industrial or non-consumer uses, unless such uses represent
308 | the only significant mode of use of the product.
309 |
310 | "Installation Information" for a User Product means any methods,
311 | procedures, authorization keys, or other information required to install
312 | and execute modified versions of a covered work in that User Product from
313 | a modified version of its Corresponding Source. The information must
314 | suffice to ensure that the continued functioning of the modified object
315 | code is in no case prevented or interfered with solely because
316 | modification has been made.
317 |
318 | If you convey an object code work under this section in, or with, or
319 | specifically for use in, a User Product, and the conveying occurs as
320 | part of a transaction in which the right of possession and use of the
321 | User Product is transferred to the recipient in perpetuity or for a
322 | fixed term (regardless of how the transaction is characterized), the
323 | Corresponding Source conveyed under this section must be accompanied
324 | by the Installation Information. But this requirement does not apply
325 | if neither you nor any third party retains the ability to install
326 | modified object code on the User Product (for example, the work has
327 | been installed in ROM).
328 |
329 | The requirement to provide Installation Information does not include a
330 | requirement to continue to provide support service, warranty, or updates
331 | for a work that has been modified or installed by the recipient, or for
332 | the User Product in which it has been modified or installed. Access to a
333 | network may be denied when the modification itself materially and
334 | adversely affects the operation of the network or violates the rules and
335 | protocols for communication across the network.
336 |
337 | Corresponding Source conveyed, and Installation Information provided,
338 | in accord with this section must be in a format that is publicly
339 | documented (and with an implementation available to the public in
340 | source code form), and must require no special password or key for
341 | unpacking, reading or copying.
342 |
343 | 7. Additional Terms.
344 |
345 | "Additional permissions" are terms that supplement the terms of this
346 | License by making exceptions from one or more of its conditions.
347 | Additional permissions that are applicable to the entire Program shall
348 | be treated as though they were included in this License, to the extent
349 | that they are valid under applicable law. If additional permissions
350 | apply only to part of the Program, that part may be used separately
351 | under those permissions, but the entire Program remains governed by
352 | this License without regard to the additional permissions.
353 |
354 | When you convey a copy of a covered work, you may at your option
355 | remove any additional permissions from that copy, or from any part of
356 | it. (Additional permissions may be written to require their own
357 | removal in certain cases when you modify the work.) You may place
358 | additional permissions on material, added by you to a covered work,
359 | for which you have or can give appropriate copyright permission.
360 |
361 | Notwithstanding any other provision of this License, for material you
362 | add to a covered work, you may (if authorized by the copyright holders of
363 | that material) supplement the terms of this License with terms:
364 |
365 | a) Disclaiming warranty or limiting liability differently from the
366 | terms of sections 15 and 16 of this License; or
367 |
368 | b) Requiring preservation of specified reasonable legal notices or
369 | author attributions in that material or in the Appropriate Legal
370 | Notices displayed by works containing it; or
371 |
372 | c) Prohibiting misrepresentation of the origin of that material, or
373 | requiring that modified versions of such material be marked in
374 | reasonable ways as different from the original version; or
375 |
376 | d) Limiting the use for publicity purposes of names of licensors or
377 | authors of the material; or
378 |
379 | e) Declining to grant rights under trademark law for use of some
380 | trade names, trademarks, or service marks; or
381 |
382 | f) Requiring indemnification of licensors and authors of that
383 | material by anyone who conveys the material (or modified versions of
384 | it) with contractual assumptions of liability to the recipient, for
385 | any liability that these contractual assumptions directly impose on
386 | those licensors and authors.
387 |
388 | All other non-permissive additional terms are considered "further
389 | restrictions" within the meaning of section 10. If the Program as you
390 | received it, or any part of it, contains a notice stating that it is
391 | governed by this License along with a term that is a further
392 | restriction, you may remove that term. If a license document contains
393 | a further restriction but permits relicensing or conveying under this
394 | License, you may add to a covered work material governed by the terms
395 | of that license document, provided that the further restriction does
396 | not survive such relicensing or conveying.
397 |
398 | If you add terms to a covered work in accord with this section, you
399 | must place, in the relevant source files, a statement of the
400 | additional terms that apply to those files, or a notice indicating
401 | where to find the applicable terms.
402 |
403 | Additional terms, permissive or non-permissive, may be stated in the
404 | form of a separately written license, or stated as exceptions;
405 | the above requirements apply either way.
406 |
407 | 8. Termination.
408 |
409 | You may not propagate or modify a covered work except as expressly
410 | provided under this License. Any attempt otherwise to propagate or
411 | modify it is void, and will automatically terminate your rights under
412 | this License (including any patent licenses granted under the third
413 | paragraph of section 11).
414 |
415 | However, if you cease all violation of this License, then your
416 | license from a particular copyright holder is reinstated (a)
417 | provisionally, unless and until the copyright holder explicitly and
418 | finally terminates your license, and (b) permanently, if the copyright
419 | holder fails to notify you of the violation by some reasonable means
420 | prior to 60 days after the cessation.
421 |
422 | Moreover, your license from a particular copyright holder is
423 | reinstated permanently if the copyright holder notifies you of the
424 | violation by some reasonable means, this is the first time you have
425 | received notice of violation of this License (for any work) from that
426 | copyright holder, and you cure the violation prior to 30 days after
427 | your receipt of the notice.
428 |
429 | Termination of your rights under this section does not terminate the
430 | licenses of parties who have received copies or rights from you under
431 | this License. If your rights have been terminated and not permanently
432 | reinstated, you do not qualify to receive new licenses for the same
433 | material under section 10.
434 |
435 | 9. Acceptance Not Required for Having Copies.
436 |
437 | You are not required to accept this License in order to receive or
438 | run a copy of the Program. Ancillary propagation of a covered work
439 | occurring solely as a consequence of using peer-to-peer transmission
440 | to receive a copy likewise does not require acceptance. However,
441 | nothing other than this License grants you permission to propagate or
442 | modify any covered work. These actions infringe copyright if you do
443 | not accept this License. Therefore, by modifying or propagating a
444 | covered work, you indicate your acceptance of this License to do so.
445 |
446 | 10. Automatic Licensing of Downstream Recipients.
447 |
448 | Each time you convey a covered work, the recipient automatically
449 | receives a license from the original licensors, to run, modify and
450 | propagate that work, subject to this License. You are not responsible
451 | for enforcing compliance by third parties with this License.
452 |
453 | An "entity transaction" is a transaction transferring control of an
454 | organization, or substantially all assets of one, or subdividing an
455 | organization, or merging organizations. If propagation of a covered
456 | work results from an entity transaction, each party to that
457 | transaction who receives a copy of the work also receives whatever
458 | licenses to the work the party's predecessor in interest had or could
459 | give under the previous paragraph, plus a right to possession of the
460 | Corresponding Source of the work from the predecessor in interest, if
461 | the predecessor has it or can get it with reasonable efforts.
462 |
463 | You may not impose any further restrictions on the exercise of the
464 | rights granted or affirmed under this License. For example, you may
465 | not impose a license fee, royalty, or other charge for exercise of
466 | rights granted under this License, and you may not initiate litigation
467 | (including a cross-claim or counterclaim in a lawsuit) alleging that
468 | any patent claim is infringed by making, using, selling, offering for
469 | sale, or importing the Program or any portion of it.
470 |
471 | 11. Patents.
472 |
473 | A "contributor" is a copyright holder who authorizes use under this
474 | License of the Program or a work on which the Program is based. The
475 | work thus licensed is called the contributor's "contributor version".
476 |
477 | A contributor's "essential patent claims" are all patent claims
478 | owned or controlled by the contributor, whether already acquired or
479 | hereafter acquired, that would be infringed by some manner, permitted
480 | by this License, of making, using, or selling its contributor version,
481 | but do not include claims that would be infringed only as a
482 | consequence of further modification of the contributor version. For
483 | purposes of this definition, "control" includes the right to grant
484 | patent sublicenses in a manner consistent with the requirements of
485 | this License.
486 |
487 | Each contributor grants you a non-exclusive, worldwide, royalty-free
488 | patent license under the contributor's essential patent claims, to
489 | make, use, sell, offer for sale, import and otherwise run, modify and
490 | propagate the contents of its contributor version.
491 |
492 | In the following three paragraphs, a "patent license" is any express
493 | agreement or commitment, however denominated, not to enforce a patent
494 | (such as an express permission to practice a patent or covenant not to
495 | sue for patent infringement). To "grant" such a patent license to a
496 | party means to make such an agreement or commitment not to enforce a
497 | patent against the party.
498 |
499 | If you convey a covered work, knowingly relying on a patent license,
500 | and the Corresponding Source of the work is not available for anyone
501 | to copy, free of charge and under the terms of this License, through a
502 | publicly available network server or other readily accessible means,
503 | then you must either (1) cause the Corresponding Source to be so
504 | available, or (2) arrange to deprive yourself of the benefit of the
505 | patent license for this particular work, or (3) arrange, in a manner
506 | consistent with the requirements of this License, to extend the patent
507 | license to downstream recipients. "Knowingly relying" means you have
508 | actual knowledge that, but for the patent license, your conveying the
509 | covered work in a country, or your recipient's use of the covered work
510 | in a country, would infringe one or more identifiable patents in that
511 | country that you have reason to believe are valid.
512 |
513 | If, pursuant to or in connection with a single transaction or
514 | arrangement, you convey, or propagate by procuring conveyance of, a
515 | covered work, and grant a patent license to some of the parties
516 | receiving the covered work authorizing them to use, propagate, modify
517 | or convey a specific copy of the covered work, then the patent license
518 | you grant is automatically extended to all recipients of the covered
519 | work and works based on it.
520 |
521 | A patent license is "discriminatory" if it does not include within
522 | the scope of its coverage, prohibits the exercise of, or is
523 | conditioned on the non-exercise of one or more of the rights that are
524 | specifically granted under this License. You may not convey a covered
525 | work if you are a party to an arrangement with a third party that is
526 | in the business of distributing software, under which you make payment
527 | to the third party based on the extent of your activity of conveying
528 | the work, and under which the third party grants, to any of the
529 | parties who would receive the covered work from you, a discriminatory
530 | patent license (a) in connection with copies of the covered work
531 | conveyed by you (or copies made from those copies), or (b) primarily
532 | for and in connection with specific products or compilations that
533 | contain the covered work, unless you entered into that arrangement,
534 | or that patent license was granted, prior to 28 March 2007.
535 |
536 | Nothing in this License shall be construed as excluding or limiting
537 | any implied license or other defenses to infringement that may
538 | otherwise be available to you under applicable patent law.
539 |
540 | 12. No Surrender of Others' Freedom.
541 |
542 | If conditions are imposed on you (whether by court order, agreement or
543 | otherwise) that contradict the conditions of this License, they do not
544 | excuse you from the conditions of this License. If you cannot convey a
545 | covered work so as to satisfy simultaneously your obligations under this
546 | License and any other pertinent obligations, then as a consequence you may
547 | not convey it at all. For example, if you agree to terms that obligate you
548 | to collect a royalty for further conveying from those to whom you convey
549 | the Program, the only way you could satisfy both those terms and this
550 | License would be to refrain entirely from conveying the Program.
551 |
552 | 13. Use with the GNU Affero General Public License.
553 |
554 | Notwithstanding any other provision of this License, you have
555 | permission to link or combine any covered work with a work licensed
556 | under version 3 of the GNU Affero General Public License into a single
557 | combined work, and to convey the resulting work. The terms of this
558 | License will continue to apply to the part which is the covered work,
559 | but the special requirements of the GNU Affero General Public License,
560 | section 13, concerning interaction through a network will apply to the
561 | combination as such.
562 |
563 | 14. Revised Versions of this License.
564 |
565 | The Free Software Foundation may publish revised and/or new versions of
566 | the GNU General Public License from time to time. Such new versions will
567 | be similar in spirit to the present version, but may differ in detail to
568 | address new problems or concerns.
569 |
570 | Each version is given a distinguishing version number. If the
571 | Program specifies that a certain numbered version of the GNU General
572 | Public License "or any later version" applies to it, you have the
573 | option of following the terms and conditions either of that numbered
574 | version or of any later version published by the Free Software
575 | Foundation. If the Program does not specify a version number of the
576 | GNU General Public License, you may choose any version ever published
577 | by the Free Software Foundation.
578 |
579 | If the Program specifies that a proxy can decide which future
580 | versions of the GNU General Public License can be used, that proxy's
581 | public statement of acceptance of a version permanently authorizes you
582 | to choose that version for the Program.
583 |
584 | Later license versions may give you additional or different
585 | permissions. However, no additional obligations are imposed on any
586 | author or copyright holder as a result of your choosing to follow a
587 | later version.
588 |
589 | 15. Disclaimer of Warranty.
590 |
591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
599 |
600 | 16. Limitation of Liability.
601 |
602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
610 | SUCH DAMAGES.
611 |
612 | 17. Interpretation of Sections 15 and 16.
613 |
614 | If the disclaimer of warranty and limitation of liability provided
615 | above cannot be given local legal effect according to their terms,
616 | reviewing courts shall apply local law that most closely approximates
617 | an absolute waiver of all civil liability in connection with the
618 | Program, unless a warranty or assumption of liability accompanies a
619 | copy of the Program in return for a fee.
620 |
621 | END OF TERMS AND CONDITIONS
622 |
623 | How to Apply These Terms to Your New Programs
624 |
625 | If you develop a new program, and you want it to be of the greatest
626 | possible use to the public, the best way to achieve this is to make it
627 | free software which everyone can redistribute and change under these terms.
628 |
629 | To do so, attach the following notices to the program. It is safest
630 | to attach them to the start of each source file to most effectively
631 | state the exclusion of warranty; and each file should have at least
632 | the "copyright" line and a pointer to where the full notice is found.
633 |
634 |
635 | Copyright (C)
636 |
637 | This program is free software: you can redistribute it and/or modify
638 | it under the terms of the GNU General Public License as published by
639 | the Free Software Foundation, either version 3 of the License, or
640 | (at your option) any later version.
641 |
642 | This program is distributed in the hope that it will be useful,
643 | but WITHOUT ANY WARRANTY; without even the implied warranty of
644 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
645 | GNU General Public License for more details.
646 |
647 | You should have received a copy of the GNU General Public License
648 | along with this program. If not, see .
649 |
650 | Also add information on how to contact you by electronic and paper mail.
651 |
652 | If the program does terminal interaction, make it output a short
653 | notice like this when it starts in an interactive mode:
654 |
655 | Copyright (C)
656 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
657 | This is free software, and you are welcome to redistribute it
658 | under certain conditions; type `show c' for details.
659 |
660 | The hypothetical commands `show w' and `show c' should show the appropriate
661 | parts of the General Public License. Of course, your program's commands
662 | might be different; for a GUI interface, you would use an "about box".
663 |
664 | You should also get your employer (if you work as a programmer) or school,
665 | if any, to sign a "copyright disclaimer" for the program, if necessary.
666 | For more information on this, and how to apply and follow the GNU GPL, see
667 | .
668 |
669 | The GNU General Public License does not permit incorporating your program
670 | into proprietary programs. If your program is a subroutine library, you
671 | may consider it more useful to permit linking proprietary applications with
672 | the library. If this is what you want to do, use the GNU Lesser General
673 | Public License instead of this License. But first, please read
674 | .
675 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ## An All-Verilog Implementation of the "Quite-OK Image Format"
2 |
3 | The full format description can be found
4 | [here](https://qoiformat.org/qoi-specification.pdf).
5 |
6 | This repository currently consists of a QOI [encoder](rtl/qoi_encoder.v)
7 | implementation. This includes the file header,
8 | [image compression](rtl/qoi_compress.v), and trailer. The result of this
9 | encoder is an AXI stream of video image "packets". A Wishbone
10 | [recorder](rtl/qoi_recorder.v) can be used to record these packets to memory.
11 | The [recorder](rtl/qoi_recorder.v) requires components from the
12 | [ZipCPU](https://github.com/ZipCPU)'s DMA at present.
13 |
14 | A separate [decoder](rtl/qoi_decoder.v) is also planned to decode and
15 | decompress images, but it remains in the early stages of its development.
16 |
17 | ## Back story
18 |
19 | The purpose of this implementation is simply to minimize the bandwidth required
20 | to store video images in memory.
21 |
22 | Let me back up. I have a SONAR project that can (currently) display some
23 | amazing things to the HDMI output--in simulation. In hardware, the displays are
24 | all messed up. Therefore, I need something that can capture the display output
25 | to memory, so that I can then come back later and debug what was actually going
26 | to the display. The problem I have is that the memory bandwidth is already well
27 | used--I don't want to take up any more of it, or risk any more of the design
28 | failing due to memory latencies. Therefore, the memory compression needs to
29 | be quick.
30 |
31 | Many of these SONAR images consist of [plots or other
32 | charts](https://github.com/ZipCPU/vgasim/tree/dev/rtl/gfx) on a black
33 | background. QOI's run-length compression should make quick work of this black
34 | background. Likewise, the images often contain only a small number of colors,
35 | such as the white lines. Again, the image compression might note the white
36 | pixel initially, but then ever after the white pixel(s) will be compressed to
37 | a single byte of white, followed by a single byte of black, followed by a run of
38 | black. Again, this should compress quite well, reducing the bandwidth to memory
39 | required by the algorithm.
40 |
41 | ## Implementation notes
42 |
43 | **Project Goal**: real-time compression and decompression.
44 |
45 | The trick in this implementation is getting the compression table, a block RAM
46 | memory, to the point where it can be accessed in one cycle. This means that
47 | the table index must be calculated ahead of time, and the multiplications
48 | before that. This necessitates a pipeline operation, which is provided in
49 | the image. At present, this pipeline is 5-stages deep for compression.
50 | Key to this operation are the two clock cycles required prior to the compression
51 | table lookup.
52 |
53 | Decoding is a bit more of a challenge, particularly since the compression
54 | table address may depend upon a previous pixel's value--even before we know
55 | the index of that previous pixel in the table. Hence, a table lookup followed
56 | by an offset value would require calculating the pixel offset prior to the
57 | table lookup. This challenge now appears to be solved at present.
58 |
59 | ## Status
60 |
61 | This IP is currently a work-in-progress. The encoder is hardware proven. The
62 | decoder passes a simulation test.
63 |
64 | The current (and planned) components of this repository include:
65 |
66 | - [qoi_compress](rtl/qoi_compress.v) compresses pixel data. This
67 | critical component has now been formally verified.
68 |
69 | Although QOI supports an alpha channel, this compression engine does not
70 | (yet) support any alpha channels.
71 |
72 | - [qoi_encoder](rtl/qoi_encoder.v) wraps the compression algorithm, providing
73 | both a file header containing image width and height, as well as an
74 | image trailer.
75 |
76 | - [qoi_recorder](rtl/qoi_recorder.v) wraps the [QOI encoder](rtl/qoi_encoder.v)
77 | so that an entire image stream may be encoded and a fixed number of images
78 | may be copied to memory. This recording capability depends upon both the
79 | [RXGears](https://github.com/ZipCPU/zipcpu/blob/master/rtl/zipdma/zipdma_rxgears.v) and the
80 | [S2MM](https://github.com/ZipCPU/zipcpu/blob/master/rtl/zipdma/zipdma_s2mm.v)
81 | components of the ZipDMA, both found in the
82 | [ZipCPU's git repository](https://github.com/ZipCPU/zipcpu).
83 |
84 | - [qoi_decompress](rtl/qoi_decompress.v) is designed to decompress QOI encoded
85 | pixel data. At present, this component passes an ad-hoc simulation check.
86 |
87 | - [qoi_decoder](rtl/qoi_decoder.v) is designed to decompress QOI frames (files).
88 | It removes the header and trailer, detects the width and height, and
89 | produces a one-frame AXI video stream as an output. This component is
90 | also part of the same ad-hoc simulation check used by other components.
91 |
92 | - [qoi_framebuffer]() is not yet written. Once written, this component will
93 | repeatedly read QOI image files from memory, and feed them to the decoder.
94 | The result (should) be a proper video stream once completed. For now, this
95 | component is nothing more than vaporware.
96 |
97 | ## Simulation
98 |
99 | A simulation model now exists that can compress a PNG file, decompress the
100 | compressed stream, and then compare the result to the original PNG file. This
101 | model has now been successful over the course of many tests. It still needs
102 | some minor upgrades to make this simulation testing automatic in order to
103 | support proper regression testing.
104 |
105 | ## License
106 |
107 | This IP is available under GPLv3. Other licenses may be available for purchase.
108 |
109 |
--------------------------------------------------------------------------------
/bench/formal/.gitignore:
--------------------------------------------------------------------------------
1 | qoi_compress*/
2 | qoi_decompress*/
3 | qoi_encoder*/
4 |
--------------------------------------------------------------------------------
/bench/formal/Makefile:
--------------------------------------------------------------------------------
1 | ################################################################################
2 | ##
3 | ## Filename: bench/formal/Makefile
4 | ## {{{
5 | ## Project: Quite OK image compression (QOI) Verilog implementation
6 | ##
7 | ## Purpose: Direct the formal evaluation of the QOI encoder and decoder.
8 | ##
9 | ## Creator: Dan Gisselquist, Ph.D.
10 | ## Gisselquist Technology, LLC
11 | ##
12 | ################################################################################
13 | ## }}}
14 | ## Copyright (C) 2024, Gisselquist Technology, LLC
15 | ## {{{
16 | ## This program is free software (firmware): you can redistribute it and/or
17 | ## modify it under the terms of the GNU General Public License as published
18 | ## by the Free Software Foundation, either version 3 of the License, or (at
19 | ## your option) any later version.
20 | ##
21 | ## This program is distributed in the hope that it will be useful, but WITHOUT
22 | ## ANY WARRANTY; without even the implied warranty of MERCHANTIBILITY or
23 | ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
24 | ## for more details.
25 | ##
26 | ## You should have received a copy of the GNU General Public License along
27 | ## with this program. (It's in the $(ROOT)/doc directory. Run make with no
28 | ## target there if the PDF file isn't present.) If not, see
29 | ## for a copy.
30 | ## }}}
31 | ## License: GPL, v3, as defined and found on www.gnu.org,
32 | ## {{{
33 | ## http://www.gnu.org/licenses/gpl.html
34 | ##
35 | ################################################################################
36 | ##
37 | ## }}}
38 |
39 | all: compress encoder
40 |
41 | .PHONY: compress qoi_compress
42 | ## {{{
43 | compress: qoi_compress
44 | qoi_compress: qoi_compress_prf/PASS
45 | CDEPS := qoi_compress.sby ../../rtl/qoi_compress.v ../../rtl/qoi_skid.v faxivideo.v
46 | qoi_compress_prf/PASS: $(CDEPS)
47 | sby -f qoi_compress.sby prf
48 | ## }}}
49 |
50 | .PHONY: encoder qoi_encoder
51 | ## {{{
52 | encoder: qoi_encoder
53 |
54 | qoi_encoder: qoi_encoder_prf/PASS qoi_encoder_prfsof/PASS qoi_encoder_cvr/PASS
55 | EDEPS := qoi_encoder.sby ../../rtl/qoi_encoder.v faxivideo.v
56 | qoi_encoder_prf/PASS: $(EDEPS)
57 | sby -f qoi_encoder.sby prf
58 | qoi_encoder_prfsof/PASS: $(EDEPS)
59 | sby -f qoi_encoder.sby prfsof
60 | qoi_encoder_cvr/PASS: $(EDEPS)
61 | sby -f qoi_encoder.sby cvr
62 | ## }}}
63 |
64 | .PHONY: decompress qoi_decompress
65 | ## {{{
66 | decompress: qoi_decompress
67 |
68 | qoi_decompress: qoi_decompress_prf/PASS # qoi_encoder_cvr/PASS
69 | DDEPS := qoi_decompress.sby ../../rtl/qoi_decompress.v
70 | qoi_decompress_prf/PASS: $(DDEPS)
71 | sby -f qoi_decompress.sby prf
72 | # qoi_decompress_cvr/PASS: $(DDEPS)
73 | # sby -f qoi_decompress.sby cvr
74 | ## }}}
75 |
--------------------------------------------------------------------------------
/bench/formal/faxivideo.v:
--------------------------------------------------------------------------------
1 | ////////////////////////////////////////////////////////////////////////////////
2 | //
3 | // Filename: bench/formal/faxivideo.v
4 | // {{{
5 | // Project: Quite OK image compression (QOI) Verilog implementation
6 | //
7 | // Purpose:
8 | //
9 | // Creator: Dan Gisselquist, Ph.D.
10 | // Gisselquist Technology, LLC
11 | //
12 | ////////////////////////////////////////////////////////////////////////////////
13 | // }}}
14 | // Copyright (C) 2022-2024, Gisselquist Technology, LLC
15 | // {{{
16 | // This program is free software (firmware): you can redistribute it and/or
17 | // modify it under the terms of the GNU General Public License as published
18 | // by the Free Software Foundation, either version 3 of the License, or (at
19 | // your option) any later version.
20 | //
21 | // This program is distributed in the hope that it will be useful, but WITHOUT
22 | // ANY WARRANTY; without even the implied warranty of MERCHANTIBILITY or
23 | // FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
24 | // for more details.
25 | //
26 | // You should have received a copy of the GNU General Public License along
27 | // with this program. (It's in the $(ROOT)/doc directory. Run make with no
28 | // target there if the PDF file isn't present.) If not, see
29 | // for a copy.
30 | // }}}
31 | // License: GPL, v3, as defined and found on www.gnu.org,
32 | // {{{
33 | // http://www.gnu.org/licenses/gpl.html
34 | //
35 | ////////////////////////////////////////////////////////////////////////////////
36 | //
37 | `default_nettype none
38 | // }}}
39 | module faxivideo #(
40 | // {{{
41 | parameter PW = 24, // Pixel width
42 | parameter LGDIM = 10,
43 | parameter [0:0] OPT_TUSER_IS_SOF = 1,
44 | // Camera sources can't handle backpressure
45 | parameter [0:0] OPT_SOURCE = 0
46 | // }}}
47 | ) (
48 | // {{{
49 | input wire i_clk, i_reset_n,
50 | input wire S_VID_TVALID,
51 | input wire S_VID_TREADY,
52 | input wire [PW-1:0] S_VID_TDATA,
53 | input wire S_VID_TLAST,
54 | input wire S_VID_TUSER,
55 | //
56 | input wire [LGDIM-1:0] i_width, i_height,
57 | output reg [LGDIM-1:0] o_xpos, o_ypos,
58 | output reg f_known_height,
59 | output wire o_hlast, o_vlast, o_sof
60 | // }}}
61 | );
62 |
63 | reg f_past_valid;
64 |
65 | initial f_past_valid = 0;
66 | always @(posedge i_clk)
67 | f_past_valid <= 1;
68 |
69 | always @(*)
70 | if (!f_past_valid)
71 | assume(!i_reset_n);
72 |
73 | // Stability
74 | // {{{
75 | always @(posedge i_clk)
76 | if (!f_past_valid || !i_reset_n)
77 | begin end else if ($past(!i_reset_n))
78 | begin
79 | assert(!S_VID_TVALID);
80 | end else if (!OPT_SOURCE && $past(S_VID_TVALID && !S_VID_TREADY))
81 | begin
82 | assert(S_VID_TVALID);
83 | assert($stable(S_VID_TDATA));
84 | assert($stable(S_VID_TLAST));
85 | assert($stable(S_VID_TUSER));
86 | end
87 | // }}}
88 |
89 | // Calculate X & Y positions
90 | // {{{
91 | initial o_xpos = 0;
92 | initial o_ypos = 0;
93 | initial f_known_height = 0;
94 | always @(posedge i_clk)
95 | if (!i_reset_n)
96 | begin
97 | o_xpos <= 0;
98 | o_ypos <= 0;
99 | f_known_height <= 0;
100 | end else if (S_VID_TVALID && (OPT_SOURCE || S_VID_TREADY))
101 | begin
102 | if (o_xpos + 1 == i_width)
103 | o_xpos <= 0;
104 | else
105 | o_xpos <= o_xpos + 1;
106 |
107 | if (o_xpos + 1 == i_width)
108 | begin
109 | if (o_ypos + 1 == i_height)
110 | begin
111 | o_ypos <= 0;
112 | f_known_height <= 1;
113 | end else
114 | o_ypos <= o_ypos + 1;
115 | end
116 | end
117 | // }}}
118 |
119 | assign o_hlast = (o_xpos == i_width - 1);
120 | assign o_vlast = (o_ypos == i_height - 1);
121 | assign o_sof = (o_xpos == 0 && o_ypos == 0);
122 |
123 | // Height/Width assumptions
124 | // {{{
125 | always @(posedge i_clk)
126 | if (f_past_valid && $past(i_reset_n) && i_reset_n)
127 | begin
128 | assume($stable(i_width));
129 | assume($stable(i_height));
130 |
131 | if (!o_sof || S_VID_TVALID)
132 | begin
133 | assume(i_width > 2);
134 | assume(i_height > 2);
135 | end
136 | end
137 | // }}}
138 |
139 | always @(posedge i_clk)
140 | if (f_past_valid && $past(i_reset_n) && i_reset_n)
141 | begin
142 | assert(o_xpos < i_width);
143 | assert(o_ypos < i_height);
144 | end
145 |
146 | always @(posedge i_clk)
147 | if (f_past_valid && i_reset_n && $past(i_reset_n) && S_VID_TVALID)
148 | begin
149 | if (OPT_TUSER_IS_SOF)
150 | begin
151 | assert(S_VID_TLAST == o_hlast);
152 | assert(!f_known_height || S_VID_TUSER == o_sof);
153 | end else begin
154 | assert(S_VID_TLAST == (o_vlast && o_hlast));
155 | assert(S_VID_TUSER == o_hlast);
156 | end
157 | end
158 |
159 | endmodule
160 |
--------------------------------------------------------------------------------
/bench/formal/qoi_compress.sby:
--------------------------------------------------------------------------------
1 | [tasks]
2 | prf
3 | # cvr
4 |
5 | [options]
6 | prf: mode prove
7 | depth 4
8 | # cvr: mode cover
9 |
10 | [engines]
11 | smtbmc
12 |
13 | [script]
14 | read -formal qoi_compress.v
15 | read -formal qoi_skid.v
16 | read -formal faxivideo.v
17 | --pycode-begin--
18 | cmd = "hierarchy -top qoi_compress"
19 | output(cmd)
20 | --pycode-end--
21 | proc -norom
22 | prep -top qoi_compress
23 |
24 | [files]
25 | faxivideo.v
26 | ../../rtl/qoi_compress.v
27 | ../../rtl/qoi_skid.v
28 |
--------------------------------------------------------------------------------
/bench/formal/qoi_encoder.sby:
--------------------------------------------------------------------------------
1 | [tasks]
2 | prf
3 | prfsof prf opt_sof
4 | cvr
5 |
6 | [options]
7 | prf: mode prove
8 | depth 5
9 | ## depth 25
10 | cvr: mode cover
11 | cvr: depth 40
12 |
13 | [engines]
14 | smtbmc
15 |
16 | [script]
17 | read -formal qoi_encoder.v
18 | read -formal faxivideo.v
19 | --pycode-begin--
20 | cmd = "hierarchy -top qoi_encoder"
21 | cmd+= " -chparam OPT_TUSER_IS_SOF %d" % (1 if "opt_sof" in tags else 0)
22 | output(cmd)
23 | --pycode-end--
24 | proc -norom
25 | prep -top qoi_encoder
26 |
27 | [files]
28 | faxivideo.v
29 | ../../rtl/qoi_encoder.v
30 |
--------------------------------------------------------------------------------
/bench/sim/.gitignore:
--------------------------------------------------------------------------------
1 | *.png
2 | *.qoi
3 |
--------------------------------------------------------------------------------
/bench/sim/Makefile:
--------------------------------------------------------------------------------
1 | ################################################################################
2 | ##
3 | ## Filename: ./bench/sim/Makefile
4 | ## {{{
5 | ## Project: Quite OK image compression (QOI) Verilog implementation
6 | ##
7 | ## Purpose: To coordinate the build of a cycle accurate, Verilator based,
8 | ## simulation of the main module. Access to the simulation is
9 | ## provided via the same software commands that will access the board, save
10 | ## that the parameters are a touch different. (See the access software for
11 | ## more information ...)
12 | ##
13 | ## Creator: Dan Gisselquist, Ph.D.
14 | ## Gisselquist Technology, LLC
15 | ##
16 | ################################################################################
17 | ## }}}
18 | ## Copyright (C) 2022-2024, Gisselquist Technology, LLC
19 | ## {{{
20 | ## This program is free software (firmware): you can redistribute it and/or
21 | ## modify it under the terms of the GNU General Public License as published
22 | ## by the Free Software Foundation, either version 3 of the License, or (at
23 | ## your option) any later version.
24 | ##
25 | ## This program is distributed in the hope that it will be useful, but WITHOUT
26 | ## ANY WARRANTY; without even the implied warranty of MERCHANTIBILITY or
27 | ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
28 | ## for more details.
29 | ##
30 | ## You should have received a copy of the GNU General Public License along
31 | ## with this program. (It's in the $(ROOT)/doc directory. Run make with no
32 | ## target there if the PDF file isn't present.) If not, see
33 | ## for a copy.
34 | ## }}}
35 | ## License: GPL, v3, as defined and found on www.gnu.org,
36 | ## {{{
37 | ## http://www.gnu.org/licenses/gpl.html
38 | ##
39 | ################################################################################
40 | ##
41 | ## }}}
42 | .PHONY: all
43 | # Make certain the "all" target is the first and therefore the default target
44 | all:
45 | CXX := g++
46 | OBJDIR := obj-pc
47 | RTLD := ../verilog
48 | VOBJDR := $(RTLD)/obj_dir
49 | VERILATOR_ROOT ?= $(shell bash -c 'verilator -V|grep VERILATOR_ROOT | head -1 | sed -e " s/^.*=\s*//"')
50 | VROOT := $(VERILATOR_ROOT)
51 | VDEFS := $(shell $(VVERSION))
52 | VINCD := $(VROOT)/include
53 | VINC := -I$(VINCD) -I$(VINCD)/vltstd -I$(VOBJDR)
54 | INCS := -I$(RTLD) $(VINC)
55 | LIBS := -lpng -lpthread
56 | VSRCS := verilated.cpp verilated_threads.cpp verilated_vcd_c.cpp
57 | VOBJS := $(addprefix $(OBJDIR)/,$(subst .cpp,.o,$(VSRCS)))
58 | CFLAGS := -g -O3 -std=gnu++14 -faligned-new -Wall $(INCS)
59 | ##
60 |
61 | PROGRAMS := main_tb
62 | # Now the return to the "all" target, and fill in some details
63 | all: $(PROGRAMS)
64 |
65 | $(OBJDIR)/%.o: %.cpp
66 | $(mk-objdir)
67 | $(CXX) $(CFLAGS) $(VDEFS) $(INCS) -c $< -o $@
68 |
69 | $(OBJDIR)/%.o: $(VINCD)/%.cpp
70 | $(mk-objdir)
71 | $(CXX) $(CFLAGS) $(INCS) -c $< -o $@
72 |
73 |
74 | $(OBJDIR)/main_tb.o: $(VOBJDR)/Vtb_top.h
75 | MAINOBJS := $(OBJDIR)/main_tb.o
76 | main_tb: $(MAINOBJS) $(VOBJS) $(VOBJDR)/Vtb_top__ALL.a
77 | $(CXX) $(CFLAGS) $^ $(VOBJDR)/Vtb_top__ALL.a -lpng -lpthread -o $@
78 |
79 | #
80 | # The "clean" target, removing any and all remaining build products
81 | #
82 | .PHONY: clean
83 | clean:
84 | rm -f *.vcd
85 | rm -f $(PROGRAMS)
86 | rm -rf $(OBJDIR)/
87 |
88 | #
89 | # The "depends" target, to know what files things depend upon. The depends
90 | # file itself is kept in $(OBJDIR)/depends.txt
91 | #
92 | define build-depends
93 | $(mk-objdir)
94 | @echo "Building dependency file"
95 | @$(CXX) $(CFLAGS) $(INCS) -MM $(SOURCES) > $(OBJDIR)/xdepends.txt
96 | @sed -e 's/^.*.o: /$(OBJDIR)\/&/' < $(OBJDIR)/xdepends.txt > $(OBJDIR)/depends.txt
97 | @rm $(OBJDIR)/xdepends.txt
98 | endef
99 |
100 | .PHONY: depends
101 | depends: tags
102 | $(build-depends)
103 |
104 | $(OBJDIR)/depends.txt: depends
105 |
106 | #
107 | define mk-objdir
108 | @bash -c "if [ ! -e $(OBJDIR) ]; then mkdir -p $(OBJDIR); fi"
109 | endef
110 |
111 |
112 | #
113 | # The "tags" target
114 | #
115 | tags: $(SOURCES) $(HEADERS)
116 | @echo "Generating tags"
117 | @ctags $(SOURCES) $(HEADERS)
118 |
119 |
120 | ifneq ($(MAKECMDGOALS),clean)
121 | # -include $(OBJDIR)/depends.txt
122 | endif
123 |
--------------------------------------------------------------------------------
/bench/sim/main_tb.cpp:
--------------------------------------------------------------------------------
1 | ////////////////////////////////////////////////////////////////////////////////
2 | //
3 | // Filename: ./bench/sim/main_tb.cpp
4 | // {{{
5 | // Project: Quite OK image compression (QOI) Verilog implementation
6 | //
7 | // Purpose: Given a PNG file (i.e. ./main_tb x.png), simulates the QOI
8 | // compression and decompression to produce x.qoi and x-out.png.
9 | // If all goes well, x.png should be identical to x-out.png.
10 | //
11 | // Given that the encoder does not support any alpha channels, x.png must
12 | // either not include ALPHA, or if it does, all ALPHA pixels must be
13 | // 0xff.
14 | //
15 | // Creator: Dan Gisselquist, Ph.D.
16 | // Gisselquist Technology, LLC
17 | //
18 | ////////////////////////////////////////////////////////////////////////////////
19 | // }}}
20 | // Copyright (C) 2024, Gisselquist Technology, LLC
21 | // {{{
22 | // This program is free software (firmware): you can redistribute it and/or
23 | // modify it under the terms of the GNU General Public License as published
24 | // by the Free Software Foundation, either version 3 of the License, or (at
25 | // your option) any later version.
26 | //
27 | // This program is distributed in the hope that it will be useful, but WITHOUT
28 | // ANY WARRANTY; without even the implied warranty of MERCHANTIBILITY or
29 | // FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
30 | // for more details.
31 | //
32 | // You should have received a copy of the GNU General Public License along
33 | // with this program. (It's in the $(ROOT)/doc directory. Run make with no
34 | // target there if the PDF file isn't present.) If not, see
35 | // for a copy.
36 | // }}}
37 | // License: GPL, v3, as defined and found on www.gnu.org,
38 | // {{{
39 | // http://www.gnu.org/licenses/gpl.html
40 | //
41 | ////////////////////////////////////////////////////////////////////////////////
42 | //
43 | #include
44 | #include
45 | #include
46 | #include
47 | #include "verilated.h"
48 | #include "verilated_vcd_c.h"
49 | #include "Vtb_top.h"
50 | // }}}
51 |
52 | unsigned get_pixel(unsigned char **row_pointers,
53 | unsigned tx_xpos, unsigned tx_ypos) {
54 | // {{{
55 | unsigned char *rowp, *pixp;
56 | unsigned p;
57 |
58 | rowp = row_pointers[tx_ypos];
59 | pixp = rowp + 3*tx_xpos;
60 | p = pixp[0] & 0x0ff;
61 | p = (p << 8) | (pixp[1] & 0x0ff);
62 | p = (p << 8) | (pixp[2] & 0x0ff);
63 |
64 | return p;
65 | }
66 | // }}}
67 |
68 | void usage(void) {
69 | fprintf(stderr, "USAGE: main_tb \n");
70 | }
71 |
72 | int main(int argc, char **argv) {
73 | Verilated::commandArgs(argc, argv);
74 | Verilated::traceEverOn(true);
75 | FILE *fpng, *fqoi;
76 | char header[8], *qoi_name;
77 | const char *trace_file = "trace.vcd";
78 | unsigned rx_xpos, rx_ypos, tx_xpos, tx_ypos, pixel,
79 | height, width, nxt_hlast, nxt_vlast, nxt_data;
80 | unsigned m_tickcount;
81 | Vtb_top *vtb;
82 | VerilatedVcdC *m_trace;
83 | bool is_png;
84 | void *error_ptr = NULL;
85 | ssize_t sz;
86 |
87 | if (argc != 2 || argv[1][0] == '-') {
88 | fprintf(stderr, "ERR: Wrong number of arguments\n");
89 | usage();
90 | exit(EXIT_FAILURE);
91 | }
92 |
93 | fpng = fopen(argv[1], "rb");
94 | if (NULL == fpng) {
95 | fprintf(stderr, "ERR: Could not open \'%s\'\n", argv[1]);
96 | exit(EXIT_FAILURE);
97 | }
98 |
99 | fprintf(stderr, "Opened %s for reading\n", argv[1]);
100 |
101 | qoi_name = strdup(argv[1]);
102 | strcpy(&qoi_name[strlen(qoi_name)-3], "qoi");
103 | fqoi = fopen(qoi_name, "w");
104 |
105 | sz = fread(header, 1, sizeof(header), fpng);
106 | is_png = (sz >= (ssize_t)sizeof(header))
107 | && !png_sig_cmp((const unsigned char *)header, 0, sizeof(header));
108 | if (!is_png) {
109 | fprintf(stderr, "ERR: \'%s\' does not appear to be a PNG file\n", argv[1]);
110 | exit(EXIT_FAILURE);
111 | }
112 |
113 | png_structp png_ptr;
114 | png_infop info_ptr, end_info;
115 | png_bytepp row_pointers;
116 |
117 | png_ptr = png_create_read_struct (PNG_LIBPNG_VER_STRING,
118 | (png_voidp)error_ptr, NULL, NULL);
119 | if (!png_ptr) {
120 | fprintf(stderr, "ERR: Could not create PNG structure\n");
121 | exit(EXIT_FAILURE);
122 | }
123 |
124 | info_ptr = png_create_info_struct (png_ptr);
125 | if (!info_ptr) {
126 | png_destroy_read_struct(&png_ptr,
127 | (png_infopp)NULL, (png_infopp)NULL);
128 | fprintf(stderr, "ERR: Could not create PNG INFO structure\n");
129 | exit(EXIT_FAILURE);
130 | }
131 |
132 | end_info = png_create_info_struct (png_ptr);
133 | if (!end_info) {
134 | png_destroy_read_struct(&png_ptr,
135 | &info_ptr, (png_infopp)NULL);
136 | fprintf(stderr, "ERR: Could not create PNG INFO structure\n");
137 | exit(EXIT_FAILURE);
138 | }
139 |
140 | if (setjmp(png_jmpbuf(png_ptr))) {
141 | fprintf(stderr, "ERR: PNG Long-jump to error\n");
142 | png_destroy_read_struct(&png_ptr, &info_ptr, &end_info);
143 | fclose(fpng);
144 | exit(EXIT_FAILURE);
145 | }
146 |
147 | png_init_io(png_ptr, fpng);
148 | png_set_user_limits(png_ptr, 65535, 65535);
149 | png_set_sig_bytes(png_ptr, sizeof(header));
150 | png_read_png(png_ptr, info_ptr,
151 | PNG_TRANSFORM_STRIP_ALPHA
152 | | PNG_TRANSFORM_PACKING
153 | | PNG_TRANSFORM_EXPAND
154 | | PNG_TRANSFORM_STRIP_16,
155 | NULL);
156 | // png_read_info(png_ptr, info_ptr);
157 |
158 | width = png_get_image_width( png_ptr, info_ptr);
159 | height = png_get_image_height(png_ptr, info_ptr);
160 |
161 | printf("Image size: %4d x %4d\n", width, height);
162 | assert(width > 4);
163 | assert(height > 4);
164 |
165 | // if (color_type == PNG_COLOR_TYPE_PALETTE)
166 | // png-set_palette_to_rgb(png_ptr);
167 |
168 | row_pointers = png_get_rows(png_ptr, info_ptr);
169 |
170 | // png_destroy_read_struct(&png_ptr, &info_ptr, &end_info);
171 | // fclose(fpng);
172 |
173 | // Open a VCD file for tracing
174 | vtb = new Vtb_top;
175 | m_trace = new VerilatedVcdC;
176 | vtb->trace(m_trace, 99);
177 | m_trace->spTrace()->set_time_resolution("ns");
178 | m_trace->spTrace()->set_time_unit("ns");
179 | m_trace->open(trace_file);
180 |
181 | vtb->i_reset = 1;
182 | vtb->i_clk = 0;
183 | vtb->s_valid = 0;
184 | vtb->s_data = 0;
185 | vtb->s_hlast = 0;
186 | vtb->s_vlast = 0;
187 | vtb->m_ready = 1;
188 | m_tickcount = 0;
189 | vtb->eval();
190 | vtb->i_clk = 1;
191 | vtb->eval();
192 | if (m_trace) m_trace->dump(10*m_tickcount);
193 | vtb->i_clk = 0;
194 | vtb->eval();
195 | if (m_trace) m_trace->dump(10*m_tickcount+5);
196 | m_tickcount++;
197 |
198 | vtb->i_clk = 1;
199 | vtb->eval();
200 | vtb->i_reset = 0;
201 | if (m_trace) m_trace->dump(10*m_tickcount);
202 | vtb->i_clk = 0;
203 | vtb->eval();
204 | if (m_trace) m_trace->dump(10*m_tickcount+5);
205 | m_tickcount++;
206 |
207 |
208 | tx_xpos = 0; tx_ypos = 0;
209 | rx_xpos = 0; rx_ypos = 0;
210 |
211 | unsigned limitcount = height * width * 10;
212 |
213 | if(0) {
214 | printf("PIX[ 17, 3] = %06x\n", get_pixel(row_pointers, 17, 3));
215 | printf("PIX[ 18, 3] = %06x\n", get_pixel(row_pointers, 18, 3));
216 |
217 | printf("PIX[1185, 3] = %06x\n", get_pixel(row_pointers, 1185, 3));
218 | printf("PIX[1186, 3] = %06x\n", get_pixel(row_pointers, 1186, 3));
219 |
220 | printf("PIX[ 14, 4] = %06x\n", get_pixel(row_pointers, 14, 4));
221 | printf("PIX[ 15, 4] = %06x\n", get_pixel(row_pointers, 15, 4));
222 | printf("PIX[ 2,44] = %06x\n", get_pixel(row_pointers, 2, 44));
223 | printf("PIX[ 3,44] = %06x\n", get_pixel(row_pointers, 3, 44));
224 | printf("PIX[ 4,44] = %06x\n", get_pixel(row_pointers, 4, 44));
225 |
226 | printf("PIX[ 584,51] = %06x\n", get_pixel(row_pointers, 584, 51));
227 | printf("PIX[ 585,51] = %06x\n", get_pixel(row_pointers, 585, 51));
228 | printf("PIX[ 586,51] = %06x\n", get_pixel(row_pointers, 586, 51));
229 | printf("PIX[ 587,51] = %06x\n", get_pixel(row_pointers, 587, 51));
230 | printf("PIX[ 588,51] = %06x\n", get_pixel(row_pointers, 588, 51));
231 | }
232 |
233 | while(!vtb->m_valid || !vtb->m_last) {
234 | // Generate pixel data for the encoder
235 | // {{{
236 | nxt_data = vtb->s_data; // row_pointers[y][x];
237 | nxt_hlast = vtb->s_hlast; // row_pointers[y][x];
238 | nxt_vlast = vtb->s_vlast; // row_pointers[y][x];
239 | if (!vtb->s_valid || vtb->s_ready) {
240 | nxt_data = get_pixel(row_pointers, tx_xpos, tx_ypos);
241 | nxt_hlast = (tx_xpos + 1) >= width;
242 | nxt_vlast = (tx_ypos + 1) >= height;
243 | if (++tx_xpos >= width) {
244 | tx_xpos = 0;
245 | if (++tx_ypos >= height)
246 | tx_ypos = 0;
247 | }
248 | }
249 | // }}}
250 |
251 | // Step the clock, setting the pixel data on pedge of it
252 | // {{{
253 | vtb->i_clk = 1;
254 | vtb->eval();
255 | // if (m_trace) m_trace->dump(10*m_tickcount);
256 | vtb->s_valid = 1;
257 | vtb->s_data = nxt_data;
258 | vtb->s_vlast = nxt_vlast;
259 | vtb->s_hlast = nxt_hlast;
260 | vtb->eval();
261 | if (m_trace) m_trace->dump(10*m_tickcount);
262 |
263 | vtb->i_clk = 0;
264 | vtb->eval();
265 | if (m_trace) {
266 | m_trace->dump(10*m_tickcount + 5);
267 | m_trace->flush();
268 | }
269 | m_tickcount++;
270 | // }}}
271 |
272 | // End sim early if we use too many clock cycles
273 | // {{{
274 | if (m_tickcount >= limitcount) {
275 | fprintf(stderr, "FAIL! Picture not produced\n");
276 | exit(EXIT_FAILURE);
277 | }
278 | // }}}
279 |
280 | // Generate a QOI file, for examining intermediate results
281 | // {{{
282 | if (vtb->qvalid && fqoi) { // && vtb->qready
283 | unsigned nb = vtb->qbytes;
284 | unsigned char qb[8];
285 |
286 | if (vtb->qbytes == 0)
287 | nb = 8;
288 | for(unsigned k=0; kqdata >> ((7-k)*8)) & 0x0ff;
290 | fwrite(qb, 1, nb, fqoi);
291 |
292 | if (vtb->qlast) {
293 | fclose(fqoi);
294 | fqoi = NULL;
295 | }
296 | }
297 | // }}}
298 |
299 | // Compare the decompressed (compressed) image w/ the original
300 | // {{{
301 | if (vtb->m_valid && vtb->m_ready) {
302 | // COPY PIXEL DATA ...
303 | pixel = get_pixel(row_pointers, rx_xpos, rx_ypos);
304 | if (vtb->m_data != pixel) {
305 | fflush(stdout);
306 | fprintf(stderr, "ERR: (PNG pixel[%3d,%3d]) %06x != 0x%06x (pixel out)\n", rx_xpos, rx_ypos, pixel, vtb->m_data);
307 | fprintf(stderr, "... %06x, %06x, %06x, %06x, %06x, %06x\n",
308 | get_pixel(row_pointers, rx_xpos+1, rx_ypos),
309 | get_pixel(row_pointers, rx_xpos+2, rx_ypos),
310 | get_pixel(row_pointers, rx_xpos+3, rx_ypos),
311 | get_pixel(row_pointers, rx_xpos+4, rx_ypos),
312 | get_pixel(row_pointers, rx_xpos+5, rx_ypos),
313 | get_pixel(row_pointers, rx_xpos+6, rx_ypos));
314 | delete vtb;
315 | exit(EXIT_FAILURE);
316 | }
317 | assert(vtb->m_user == (((rx_xpos +1) >= width) ? 1:0));
318 | if (vtb->m_user)
319 | assert(vtb->m_last
320 | == (((rx_ypos +1) >= height) ? 1:0));
321 | //
322 | // THEN ...
323 | if (vtb->m_last && vtb->m_user) { // VLAST && HLAST
324 | // We're about to exit
325 | } else if (vtb->m_user) { // HLAST
326 | rx_xpos = 0;
327 | rx_ypos++;
328 | } else
329 | rx_xpos++;
330 | }
331 | // }}}
332 | }
333 |
334 | if(fqoi != NULL)
335 | fclose(fqoi);
336 | printf("SUCCESS!\n");
337 | exit(EXIT_SUCCESS);
338 | }
339 |
--------------------------------------------------------------------------------
/bench/verilog/Makefile:
--------------------------------------------------------------------------------
1 | ################################################################################
2 | ##
3 | ## Filename: ./bench/verilog/Makefile
4 | ## {{{
5 | ## Project: Quite OK image compression (QOI) Verilog implementation
6 | ##
7 | ## Purpose:
8 | ##
9 | ## Creator: Dan Gisselquist, Ph.D.
10 | ## Gisselquist Technology, LLC
11 | ##
12 | ################################################################################
13 | ## }}}
14 | ## Copyright (C) 2024, Gisselquist Technology, LLC
15 | ## {{{
16 | ## This program is free software (firmware): you can redistribute it and/or
17 | ## modify it under the terms of the GNU General Public License as published
18 | ## by the Free Software Foundation, either version 3 of the License, or (at
19 | ## your option) any later version.
20 | ##
21 | ## This program is distributed in the hope that it will be useful, but WITHOUT
22 | ## ANY WARRANTY; without even the implied warranty of MERCHANTIBILITY or
23 | ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
24 | ## for more details.
25 | ##
26 | ## You should have received a copy of the GNU General Public License along
27 | ## with this program. (It's in the $(ROOT)/doc directory. Run make with no
28 | ## target there if the PDF file isn't present.) If not, see
29 | ## for a copy.
30 | ## }}}
31 | ## License: GPL, v3, as defined and found on www.gnu.org,
32 | ## {{{
33 | ## http://www.gnu.org/licenses/gpl.html
34 | ##
35 | ################################################################################
36 | ##
37 | ## }}}
38 | all: build
39 | YYMMDD=`date +%Y%m%d`
40 | CXX := g++
41 | FBDIR := .
42 | VDIRFB:= $(FBDIR)/obj_dir
43 | VOBJ := obj_dir
44 | CPUDR := cpu
45 | BASE := tb_top
46 |
47 | .DELETE_ON_ERROR:
48 | .PHONY: build
49 | build: $(VOBJ)/V$(BASE)__ALL.a
50 | SUBMAKE := $(MAKE) --no-print-directory -C $(VOBJ) -f
51 | ifeq ($(VERILATOR_ROOT),)
52 | VERILATOR := verilator
53 | else
54 | VERILATOR := $(VERILATOR_ROOT)/bin/verilator
55 | endif
56 | VFLAGS = -Wall -Wno-TIMESCALEMOD --MMD -O3 -D --trace -Mdir $(VDIRFB) -y ../../rtl --assert -cc
57 |
58 | ## Generic pattern(s)
59 | ## {{{
60 | $(VOBJ)/V$(BASE)__ALL.a: $(VOBJ)/V$(BASE).h
61 | $(VOBJ)/V$(BASE).mk: $(VOBJ)/V$(BASE).cpp
62 | $(VOBJ)/V$(BASE).cpp: $(VOBJ)/V$(BASE).h
63 | ## }}}
64 |
65 | $(VOBJ)/V%__ALL.a: $(VOBJ)/V%.mk
66 | +$(SUBMAKE) V$*.mk
67 |
68 | $(VOBJ)/Vtb_top.h: tb_top.v
69 | $(VERILATOR) $(VFLAGS) tb_top.v
70 | $(VOBJ)/V%.h: %.v
71 | $(VERILATOR) $(VFLAGS) $*.v
72 |
73 | $(VOBJ)/V%.cpp: $(VOBJ)/V%.h
74 | $(VOBJ)/V%.mk: $(VOBJ)/V%.h
75 | $(VOBJ)/V%.h: $(FBDIR)/%.v
76 |
77 | .PHONY: clean
78 | ## {{{
79 | clean:
80 | rm -rf $(VDIRFB)/*.mk
81 | rm -rf $(VDIRFB)/*.cpp
82 | rm -rf $(VDIRFB)/*.h
83 | rm -rf $(VDIRFB)/
84 | ## }}}
85 |
86 | #
87 | # Note Verilator's dependency created information, and include it here if we
88 | # can
89 | DEPS := $(wildcard $(VOBJ)/*.d)
90 | ifneq ($(MAKECMDGOALS),clean)
91 | ifneq ($(DEPS),)
92 | include $(DEPS)
93 | endif
94 | endif
95 |
--------------------------------------------------------------------------------
/bench/verilog/tb_top.v:
--------------------------------------------------------------------------------
1 | ////////////////////////////////////////////////////////////////////////////////
2 | //
3 | // Filename: ./bench/verilog/tb_top.v
4 | // {{{
5 | // Project: Quite OK image compression (QOI) Verilog implementation
6 | //
7 | // Purpose:
8 | //
9 | // Creator: Dan Gisselquist, Ph.D.
10 | // Gisselquist Technology, LLC
11 | //
12 | ////////////////////////////////////////////////////////////////////////////////
13 | // }}}
14 | // Copyright (C) 2024, Gisselquist Technology, LLC
15 | // {{{
16 | // This program is free software (firmware): you can redistribute it and/or
17 | // modify it under the terms of the GNU General Public License as published
18 | // by the Free Software Foundation, either version 3 of the License, or (at
19 | // your option) any later version.
20 | //
21 | // This program is distributed in the hope that it will be useful, but WITHOUT
22 | // ANY WARRANTY; without even the implied warranty of MERCHANTIBILITY or
23 | // FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
24 | // for more details.
25 | //
26 | // You should have received a copy of the GNU General Public License along
27 | // with this program. (It's in the $(ROOT)/doc directory. Run make with no
28 | // target there if the PDF file isn't present.) If not, see
29 | // for a copy.
30 | // }}}
31 | // License: GPL, v3, as defined and found on www.gnu.org,
32 | // {{{
33 | // http://www.gnu.org/licenses/gpl.html
34 | //
35 | ////////////////////////////////////////////////////////////////////////////////
36 | //
37 | `default_nettype none
38 | // }}}
39 | module tb_top #(
40 | parameter DW=64
41 | ) (
42 | input wire i_clk, i_reset,
43 | // Video stream input
44 | // {{{
45 | input wire s_valid,
46 | output wire s_ready,
47 | input wire [23:0] s_data,
48 | input wire s_hlast,
49 | input wire s_vlast,
50 | // }}}
51 | // QOI compressed output stream
52 | // {{{
53 | output wire qvalid,
54 | output wire [DW-1:0] qdata,
55 | output wire [$clog2(DW/8)-1:0] qbytes,
56 | output wire qlast,
57 | // }}}
58 | // Video stream output
59 | // {{{
60 | output reg m_valid,
61 | input wire m_ready,
62 | output reg [23:0] m_data,
63 | output reg m_user, m_last
64 | // }}}
65 | );
66 |
67 | wire w_qvalid, qready;
68 |
69 | qoi_encoder
70 | u_encoder (
71 | .i_clk(i_clk), .i_reset(i_reset),
72 | //
73 | .s_valid(s_valid),
74 | .s_ready(s_ready),
75 | .s_data(s_data),
76 | .s_last(s_vlast && s_hlast),
77 | .s_user(s_hlast),
78 | //
79 | .o_qvalid(w_qvalid),
80 | .i_qready(qready),
81 | .o_qdata(qdata),
82 | .o_qbytes(qbytes),
83 | .o_qlast(qlast)
84 | );
85 |
86 | assign qvalid = w_qvalid && qready;
87 |
88 | qoi_decoder
89 | u_decoder (
90 | .i_clk(i_clk), .i_reset(i_reset),
91 | //
92 | .i_qvalid(w_qvalid),
93 | .o_qready(qready),
94 | .i_qdata(qdata),
95 | .i_qbytes(qbytes),
96 | // .i_qlast(qlast),
97 | //
98 | .m_valid(m_valid),
99 | .m_ready(m_ready),
100 | .m_data(m_data),
101 | .m_last(m_last),
102 | .m_user(m_user)
103 | );
104 |
105 | endmodule
106 |
--------------------------------------------------------------------------------
/doc/devstat.dia:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ZipCPU/qoiimg/cbba1092265818a176e81f328f13422151c93694/doc/devstat.dia
--------------------------------------------------------------------------------
/doc/devstat.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ZipCPU/qoiimg/cbba1092265818a176e81f328f13422151c93694/doc/devstat.png
--------------------------------------------------------------------------------
/rtl/qoi_compress.v:
--------------------------------------------------------------------------------
1 | ////////////////////////////////////////////////////////////////////////////////
2 | //
3 | // Filename: ./rtl/qoi_compress.v
4 | // {{{
5 | // Project: Quite OK image compression (QOI) Verilog implementation
6 | //
7 | // Purpose: This encoder turns image data into compressed image data. It
8 | // doesn't handle header or trailer insertions. As such, it
9 | // requires an external wrapper (somewhere) to guarantee proper formatting.
10 | //
11 | // This implementation does not handle ALPHA.
12 | //
13 | // The input is an AXI video stream, save two signals:
14 | // HLAST: true on the last pixel of every line.
15 | // VLAST: true on either the last line, or the last pixel of the last line.
16 | // Hence HLAST && VLAST is the (reliable/guaranteed) signal for the last
17 | // pixel in any frame.
18 | //
19 | // The output is an AXI byte stream containing between 1-4 bytes per beat,
20 | // with the first byte always packed into the MSB (i.e. big endian), and
21 | // other bytes packed immediately following.
22 | // BYTES: Contains the number of valid bytes in each beat, with 2'b0
23 | // representing a full 4-byte word.
24 | // LAST: True on the last DATA beat of any image.
25 | // Line boundaries are not preserved in this implementation.
26 | //
27 | // Creator: Dan Gisselquist, Ph.D.
28 | // Gisselquist Technology, LLC
29 | //
30 | ////////////////////////////////////////////////////////////////////////////////
31 | // }}}
32 | // Copyright (C) 2024, Gisselquist Technology, LLC
33 | // {{{
34 | // This program is free software (firmware): you can redistribute it and/or
35 | // modify it under the terms of the GNU General Public License as published
36 | // by the Free Software Foundation, either version 3 of the License, or (at
37 | // your option) any later version.
38 | //
39 | // This program is distributed in the hope that it will be useful, but WITHOUT
40 | // ANY WARRANTY; without even the implied warranty of MERCHANTIBILITY or
41 | // FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
42 | // for more details.
43 | //
44 | // You should have received a copy of the GNU General Public License along
45 | // with this program. (It's in the $(ROOT)/doc directory. Run make with no
46 | // target there if the PDF file isn't present.) If not, see
47 | // for a copy.
48 | // }}}
49 | // License: GPL, v3, as defined and found on www.gnu.org,
50 | // {{{
51 | // http://www.gnu.org/licenses/gpl.html
52 | //
53 | ////////////////////////////////////////////////////////////////////////////////
54 | //
55 | `default_nettype none
56 | // }}}
57 | module qoi_compress (
58 | input wire i_clk, i_reset,
59 | // Video stream input
60 | // {{{
61 | input wire s_vid_valid,
62 | output wire s_vid_ready,
63 | input wire [23:0] s_vid_data,
64 | input wire s_vid_hlast,
65 | input wire s_vid_vlast,
66 | // }}}
67 | // QOI compressed output stream
68 | // {{{
69 | output reg m_valid,
70 | input wire m_ready,
71 | output reg [31:0] m_data,
72 | output reg [1:0] m_bytes,
73 | output reg m_last
74 | // }}}
75 | );
76 |
77 | // Local declarations
78 | // {{{
79 | wire skd_valid, skd_ready, skd_hlast, skd_vlast;
80 | wire [23:0] skd_data;
81 |
82 | reg s1_valid, s1_last;
83 | reg [5:0] s1_rhash, s1_ghash, s1_bhash;
84 | reg [23:0] s1_pixel;
85 | wire s1_ready;
86 |
87 | reg s2_valid, s2_last;
88 | reg [5:0] s2_tbl_index;
89 | reg [23:0] s2_pixel;
90 | reg [7:0] s2_gdiff;
91 | wire s2_ready;
92 |
93 | reg s3_valid, s3_last, s3_tbl_valid, s3_rptvalid;
94 | reg [23:0] s3_pixel, s3_tbl_pixel;
95 | reg [5:0] s3_repeats, s3_tblidx;
96 | reg [7:0] s3_rdiff, s3_gdiff, s3_bdiff, s3_rgdiff, s3_bgdiff;
97 | wire s3_continue, s3_ready;
98 |
99 | reg [63:0] tbl_valid;
100 | reg [23:0] tbl_pixel [0:63];
101 |
102 | reg s4_valid, s4_tblset, s4_rptset, s4_last,
103 | s4_small, s4_bigdf;
104 | reg [5:0] s4_tblidx, s4_repeats, s4_gdiff;
105 | reg [23:0] s4_pixel;
106 | reg [3:0] s4_rgdiff, s4_bgdiff;
107 | reg [1:0] s4_rdiff, s4_bdiff;
108 | wire s4_ready;
109 |
110 | wire gbl_ready;
111 | reg gbl_last;
112 | // }}}
113 | ////////////////////////////////////////////////////////////////////////
114 | //
115 | // Skidbuffer
116 | // {{{
117 | ////////////////////////////////////////////////////////////////////////
118 | //
119 | //
120 |
121 | qoi_skid #(
122 | `ifdef FORMAL
123 | .OPT_PASSTHROUGH(1'b1),
124 | `endif
125 | .OPT_OUTREG(1'b0), .DW(2+24)
126 | ) u_skid (
127 | // {{{
128 | .i_clk(i_clk), .i_reset(i_reset),
129 | .i_valid(s_vid_valid), .o_ready(s_vid_ready),
130 | .i_data({ s_vid_hlast, s_vid_vlast, s_vid_data }),
131 | .o_valid(skd_valid), .i_ready(skd_ready),
132 | .o_data({ skd_hlast, skd_vlast, skd_data })
133 | // }}}
134 | );
135 |
136 | // }}}
137 | ////////////////////////////////////////////////////////////////////////
138 | //
139 | // Step #1: Pre-calculate hash data
140 | // {{{
141 | ////////////////////////////////////////////////////////////////////////
142 | //
143 | //
144 |
145 | initial s1_valid = 1'b0;
146 | always @(posedge i_clk)
147 | if (i_reset)
148 | s1_valid <= 0;
149 | else if (skd_valid && skd_ready)
150 | s1_valid <= skd_valid;
151 | else if (s1_ready)
152 | s1_valid <= 0;
153 |
154 | initial s1_last = 1'b0;
155 | always @(posedge i_clk)
156 | if (i_reset)
157 | s1_last <= 0;
158 | else if (skd_valid && skd_ready)
159 | s1_last <= skd_hlast && skd_vlast;
160 | else if (s1_ready)
161 | s1_last <= 0;
162 |
163 | initial s1_pixel = 0;
164 | always @(posedge i_clk)
165 | if (i_reset)
166 | s1_pixel <= 0;
167 | else if (skd_valid && skd_ready)
168 | s1_pixel <= skd_data;
169 | else if (s1_ready && s1_last)
170 | s1_pixel <= 0;
171 |
172 | always @(posedge i_clk)
173 | if (skd_valid && skd_ready)
174 | begin
175 | s1_rhash <= skd_data[21:16] + { skd_data[20:16], 1'b0 };
176 | s1_ghash <= skd_data[13: 8] + { skd_data[11: 8], 2'b0 };
177 | s1_bhash <= { skd_data[2:0], 3'h0} - skd_data[ 5: 0];
178 | end
179 | // }}}
180 | ////////////////////////////////////////////////////////////////////////
181 | //
182 | // Step #2: Finish calculating the hash table index
183 | // {{{
184 | ////////////////////////////////////////////////////////////////////////
185 | //
186 | //
187 |
188 | initial s2_valid = 1'b0;
189 | always @(posedge i_clk)
190 | if (i_reset)
191 | s2_valid <= 0;
192 | else if (s1_valid && s1_ready)
193 | s2_valid <= 1'b1;
194 | else if (s2_ready)
195 | s2_valid <= 1'b0;
196 |
197 | initial s2_pixel = 0;
198 | always @(posedge i_clk)
199 | if (i_reset)
200 | s2_pixel <= 0;
201 | else if (s1_valid && s1_ready)
202 | s2_pixel <= s1_pixel;
203 | else if (s2_ready && s2_last)
204 | s2_pixel <= 0;
205 |
206 | always @(posedge i_clk)
207 | if (i_reset)
208 | s2_last <= 1'b0;
209 | else if (s1_valid && s1_ready)
210 | s2_last <= s1_last;
211 | else if (s2_ready)
212 | s2_last <= 1'b0;
213 |
214 | always @(posedge i_clk)
215 | if (s1_valid && s1_ready)
216 | begin
217 | s2_tbl_index <= s1_rhash + s1_ghash + s1_bhash + 6'h35;
218 |
219 | s2_gdiff <= s1_pixel[15: 8] - s2_pixel[15: 8];
220 | end
221 |
222 | // }}}
223 | ////////////////////////////////////////////////////////////////////////
224 | //
225 | // Step #3: Hash table lookup, calc differences, count repeats
226 | // {{{
227 | ////////////////////////////////////////////////////////////////////////
228 | //
229 | //
230 |
231 | initial s3_valid = 0;
232 | always @(posedge i_clk)
233 | if (i_reset)
234 | s3_valid <= 0;
235 | else if (s2_valid && s2_ready)
236 | s3_valid <= 1'b1;
237 | else if (s3_ready)
238 | s3_valid <= 1'b0;
239 |
240 | initial s3_rptvalid = 0;
241 | initial s3_repeats = 0;
242 | always @(posedge i_clk)
243 | if (i_reset)
244 | begin
245 | s3_repeats <= 0;
246 | s3_rptvalid <= 0;
247 | end else if (s2_valid && s2_ready)
248 | begin
249 | if (!s3_continue)
250 | begin
251 | s3_rptvalid <= s3_rptvalid && (s3_pixel == s2_pixel);
252 | s3_repeats <= 0;
253 | end else if (!s3_rptvalid)
254 | begin
255 | s3_rptvalid <= 1;
256 | s3_repeats <= 0;
257 | end else begin
258 | s3_rptvalid <= 1;
259 | s3_repeats <= s3_repeats + 1;
260 | end
261 | end else if (s3_valid && s3_ready && s3_last)
262 | begin
263 | s3_repeats <= 0;
264 | s3_rptvalid <= 0;
265 | end
266 |
267 | // Table lookup
268 | // {{{
269 | always @(posedge i_clk)
270 | if (s2_valid && s2_ready)
271 | s3_tbl_valid <= tbl_valid[s2_tbl_index];
272 |
273 | always @(posedge i_clk)
274 | if (s2_valid && s2_ready)
275 | s3_tbl_pixel <= tbl_pixel[s2_tbl_index];
276 | // }}}
277 |
278 | // Write back to the table
279 | // {{{
280 | initial tbl_valid = 0;
281 | always @(posedge i_clk)
282 | if (i_reset)
283 | tbl_valid <= 0;
284 | else if (s2_valid && s2_ready && s2_last)
285 | tbl_valid <= 0;
286 | else if (s2_valid && s2_ready)
287 | tbl_valid[s2_tbl_index] <= 1'b1;
288 |
289 | always @(posedge i_clk)
290 | if (s2_valid && s2_ready)
291 | tbl_pixel[s2_tbl_index] <= s2_pixel;
292 | // }}}
293 |
294 | // s3_(everything else): tblidx, xdiff, xgdiff, xlast, && pixel
295 | // {{{
296 | initial s3_pixel = 0;
297 | always @(posedge i_clk)
298 | if (i_reset)
299 | s3_pixel <= 0;
300 | else if (s2_valid && s2_ready)
301 | s3_pixel <= s2_pixel;
302 | else if (s3_ready && s3_last)
303 | s3_pixel <= 0;
304 |
305 | always @(posedge i_clk)
306 | if (i_reset)
307 | s3_last <= 1'b0;
308 | else if (s2_valid && s2_ready)
309 | s3_last <= s2_last;
310 | else if (s3_ready)
311 | s3_last <= 1'b0;
312 |
313 | always @(posedge i_clk)
314 | if (s2_valid && s2_ready)
315 | begin
316 | s3_tblidx <= s2_tbl_index;
317 |
318 | s3_rdiff <= s2_pixel[23:16] - s3_pixel[23:16];
319 | // s3_gdiff <= s2_pixel[15: 8] - s3_pixel[15: 8];
320 | s3_gdiff <= s2_gdiff;
321 | s3_bdiff <= s2_pixel[ 7: 0] - s3_pixel[ 7: 0];
322 |
323 | s3_rgdiff <= (s2_pixel[23:16] - s3_pixel[23:16]) - s2_gdiff;
324 | s3_bgdiff <= (s2_pixel[ 7: 0] - s3_pixel[ 7: 0]) - s2_gdiff;
325 | end
326 | // }}}
327 |
328 | assign s3_continue = (s3_pixel == s2_pixel) &&(s3_repeats < 6'd61)
329 | && !s3_last;
330 | // }}}
331 | ////////////////////////////////////////////////////////////////////////
332 | //
333 | // Step #4: Hash table compare, difference check
334 | // {{{
335 | ////////////////////////////////////////////////////////////////////////
336 | //
337 | //
338 |
339 | initial s4_valid = 0;
340 | always @(posedge i_clk)
341 | if (i_reset)
342 | s4_valid <= 0;
343 | else if (s3_valid && s3_ready)
344 | s4_valid <= (!s3_rptvalid || !s3_continue);
345 | else if (s4_ready)
346 | s4_valid <= 1'b0;
347 |
348 | initial s4_pixel = 0;
349 | always @(posedge i_clk)
350 | if (i_reset)
351 | s4_pixel <= 0;
352 | else if (s3_valid && s3_ready && (!s3_rptvalid || !s3_continue))
353 | s4_pixel <= s3_pixel;
354 | else if (s4_ready && s4_last)
355 | s4_pixel <= 0;
356 |
357 | always @(posedge i_clk)
358 | if (i_reset)
359 | s4_last <= 1'b0;
360 | else if (s3_valid && s3_ready)
361 | s4_last <= s3_last;
362 | else if (s4_ready)
363 | s4_last <= 1'b0;
364 |
365 | initial s4_rptset = 0;
366 | initial s4_repeats = 0;
367 | initial s4_repeats = 0;
368 | always @(posedge i_clk)
369 | if (s3_valid && s3_ready)
370 | begin
371 | s4_tblset <= (s3_pixel == s3_tbl_pixel) && s3_tbl_valid
372 | && (s3_tblidx != s4_tblidx);
373 | s4_tblidx <= s3_tblidx;
374 |
375 | s4_rptset <= s3_rptvalid && !s3_continue;
376 | s4_repeats <= s3_repeats;
377 |
378 | s4_small <= ((&s3_rdiff[7:1]) || (s3_rdiff <= 1))
379 | && ((&s3_gdiff[7:1]) || (s3_gdiff <= 1))
380 | && ((&s3_bdiff[7:1]) || (s3_bdiff <= 1));
381 | s4_bigdf <= ((&s3_gdiff[7:5]) || (s3_gdiff <= 8'd31)) // 6b
382 | && ((&s3_rgdiff[7:3]) || (s3_rgdiff <= 8'd7)) // 4b
383 | && ((&s3_bgdiff[7:3]) || (s3_bgdiff <= 8'd7)); // 4b
384 |
385 | s4_rdiff <= s3_rdiff[1:0];
386 | s4_gdiff <= s3_gdiff[5:0];
387 | s4_bdiff <= s3_bdiff[1:0];
388 | //
389 | s4_rgdiff <= s3_rgdiff[3:0];
390 | s4_bgdiff <= s3_bgdiff[3:0];
391 | end
392 |
393 | // }}}
394 | ////////////////////////////////////////////////////////////////////////
395 | //
396 | // Step #5: Encode the output
397 | // {{{
398 | ////////////////////////////////////////////////////////////////////////
399 | //
400 | //
401 |
402 | initial m_valid = 1'b0;
403 | always @(posedge i_clk)
404 | if (i_reset)
405 | m_valid <= 1'b0;
406 | else if (!m_valid || m_ready)
407 | m_valid <= s4_valid && s4_ready;
408 |
409 | always @(posedge i_clk)
410 | if (i_reset)
411 | m_last <= 1'b0;
412 | else if (s4_valid && s4_ready)
413 | m_last <= s4_last;
414 | else if (m_ready)
415 | m_last <= 1'b0;
416 |
417 | always @(posedge i_clk)
418 | if (s4_valid && s4_ready)
419 | begin
420 | if (s4_rptset)
421 | begin
422 | m_data <= { 2'b11, s4_repeats, 24'h0 };
423 | m_bytes <= 2'd1;
424 | end else if (s4_tblset)
425 | begin
426 | // $display("Encode: TBL[%02x] for %06x", s4_tblidx, s4_pixel);
427 | m_data <= { 2'b00, s4_tblidx, 24'h0 };
428 | m_bytes <= 2'd1;
429 | end else if (s4_small)
430 | begin
431 | m_data <= { 2'b01, s4_rdiff[1:0], s4_gdiff[1:0],
432 | s4_bdiff[1:0], 24'h0 };
433 | m_data[29:28] <= s4_rdiff[1:0] + 2'b10;
434 | m_data[27:26] <= s4_gdiff[1:0] + 2'b10;
435 | m_data[25:24] <= s4_bdiff[1:0] + 2'b10;
436 | m_bytes <= 2'd1;
437 | end else if (s4_bigdf)
438 | begin
439 | m_data <= { 2'b10, s4_gdiff[5:0],
440 | s4_rgdiff[3:0],
441 | s4_bgdiff[3:0], 16'h0 };
442 | m_data[29:24] <= s4_gdiff[5:0] + 6'h20;
443 | m_data[23:20] <= s4_rgdiff[3:0] + 4'h8;
444 | m_data[19:16] <= s4_bgdiff[3:0] + 4'h8;
445 | m_bytes <= 2'd2;
446 | end else begin
447 | m_data <= { 8'hfe, s4_pixel };
448 | m_bytes <= 2'd0;
449 | end
450 | end
451 | // }}}
452 | ////////////////////////////////////////////////////////////////////////
453 | //
454 | // Pipeline control (i.e. ready signals)
455 | // {{{
456 |
457 | assign skd_ready = skd_valid && (!m_valid || m_ready) && !gbl_last;
458 | assign s1_ready = gbl_ready;
459 | assign s2_ready = gbl_ready;
460 | assign s3_ready = gbl_ready;
461 | assign s4_ready = gbl_ready;
462 | // assign s4_ready = (!s4_valid || m_ready)
463 | // && (s2_valid || s3_last) && s3_valid;
464 | assign gbl_ready = (skd_valid || gbl_last) && (!m_valid || m_ready);
465 |
466 | initial gbl_last = 1'b0;
467 | always @(posedge i_clk)
468 | if (i_reset)
469 | gbl_last <= 1'b0;
470 | else if (skd_valid && skd_ready)
471 | gbl_last <= skd_hlast && skd_vlast;
472 | else if (m_valid && m_ready && m_last)
473 | gbl_last <= 1'b0;
474 | // }}}
475 | ////////////////////////////////////////////////////////////////////////////////
476 | ////////////////////////////////////////////////////////////////////////////////
477 | ////////////////////////////////////////////////////////////////////////////////
478 | //
479 | // Formal properties
480 | // {{{
481 | ////////////////////////////////////////////////////////////////////////////////
482 | ////////////////////////////////////////////////////////////////////////////////
483 | ////////////////////////////////////////////////////////////////////////////////
484 | `ifdef FORMAL
485 | reg f_past_valid;
486 | (* anyconst *) reg [23:0] fnvr_pixel;
487 | (* anyconst *) reg [5:0] fc_index;
488 | reg fc_valid;
489 | reg [23:0] fc_pixel;
490 |
491 | reg [5:0] f1_rhash, f1_ghash, f1_bhash;
492 | reg [31:0] f1_pcount;
493 |
494 | reg [5:0] f2_rhash, f2_ghash, f2_bhash, f2_index;
495 | reg [7:0] f2_gdiff;
496 | reg [31:0] f2_pcount;
497 |
498 | reg [5:0] f3_rhash, f3_ghash, f3_bhash, f3_index;
499 | reg [7:0] f3_gdiff, f3_rdiff, f3_bdiff;;
500 | reg [7:0] f3_rgdiff, f3_bgdiff;
501 | reg [31:0] f3_pcount;
502 |
503 | reg [5:0] f4_rhash, f4_ghash, f4_bhash, f4_index;
504 | reg [7:0] f4_gdiff, f4_rdiff, f4_bdiff;;
505 | reg [7:0] f4_rgdiff, f4_bgdiff;
506 | reg [31:0] f4_pcount;
507 |
508 | reg [23:0] fm_pixel, flst_pixel, fm_luna, fm_delta;
509 | reg [31:0] fm_pcount;
510 | reg [ 7:0] fm_vg;
511 |
512 | (* anyconst *) reg fnvr_last;
513 |
514 |
515 | initial f_past_valid = 1'b0;
516 | always @(posedge i_clk)
517 | f_past_valid <= 1'b1;
518 |
519 | always @(*)
520 | if (!f_past_valid)
521 | assume(i_reset);
522 | ////////////////////////////////////////////////////////////////////////
523 | //
524 | // Global Pipeline handling assertions
525 | // {{{
526 | always @(*)
527 | if (f_past_valid)
528 | case({ s1_valid, s2_valid, s3_valid, s4_valid })
529 | 4'b0000: assert(f1_pcount == 0);
530 | 4'b1000: begin
531 | assert(f1_pcount == 1);
532 | assert(!m_valid);
533 | assert(!m_last);
534 | assert(!s1_last);
535 | end
536 | 4'b1100: begin
537 | assert(f1_pcount == 2);
538 | assert(f2_pcount == 1);
539 | assert(!m_valid && !m_last && !s2_last && !s1_last);
540 | assert(!m_last);
541 | assert(!s2_last);
542 | assert(!s1_last);
543 | end
544 | 4'b1110: begin
545 | assert(f1_pcount >= 3);
546 | // assert(!m_valid);
547 | assert(!m_last);
548 | assert(!s4_last);
549 | assert(!s3_last);
550 | assert(!s2_last);
551 | assert(s1_last == gbl_last);
552 | end
553 | 4'b1111: begin
554 | assert(f1_pcount >= 4);
555 | assert(!s3_rptvalid || s3_repeats == 0);
556 | assert(!m_last);
557 | assert(!s4_last);
558 | assert(!s3_last);
559 | assert(!s2_last);
560 | assert(s1_last == gbl_last);
561 | end
562 | 4'b0110: begin
563 | assert(f1_pcount == 0);
564 | assert(f2_pcount >= 3);
565 | assert(gbl_last);
566 | assert(s2_last);
567 | assert(!s3_last);
568 | assert(s3_rptvalid);
569 | assert(!m_last);
570 | // assert(!s4_last);
571 | end
572 | 4'b0111: begin
573 | assert(f1_pcount == 0);
574 | assert(f2_pcount >= 3);
575 | assert(gbl_last);
576 | assert(s2_last);
577 | assert(!s3_last);
578 | assert(!s4_last);
579 | assert(!s3_rptvalid || s3_repeats == 0);
580 | assert(!m_last);
581 | end
582 | 4'b0010: begin
583 | assert(f1_pcount == 0);
584 | assert(f3_pcount >= 3);
585 | assert(gbl_last);
586 | assert(s3_last);
587 | assert(!m_last);
588 | end
589 | 4'b0011: begin
590 | assert(f1_pcount == 0);
591 | assert(f3_pcount >= 3);
592 | assert(gbl_last);
593 | assert(s3_last);
594 | assert(!s4_last);
595 | assert(!s3_rptvalid || s3_repeats == 0);
596 | assert(!m_last);
597 | end
598 | 4'b0001: begin
599 | assert(gbl_last);
600 | assert(s4_last);
601 | assert(!s3_rptvalid && s3_repeats == 0);
602 | assert(!m_last);
603 | end
604 | default: assert(0);
605 | endcase
606 |
607 | always @(*)
608 | if (f_past_valid && !s1_valid && !s2_valid && !s3_valid && !s4_valid
609 | && (!m_valid || !m_last))
610 | assert(!gbl_last);
611 |
612 | always @(*)
613 | if(f_past_valid && (s1_last || s2_last || s3_last || s4_last || m_last))
614 | assert(gbl_last);
615 |
616 | always @(*)
617 | if (f_past_valid)
618 | begin
619 | if (!s1_valid) assert(!s1_last);
620 | if (!s2_valid) assert(!s2_last);
621 | if (!s3_valid) assert(!s3_last);
622 | if (!s4_valid) assert(!s4_last);
623 | if (!m_valid) assert(!m_last);
624 | end
625 |
626 | always @(*)
627 | if (!s3_valid) assume(!skd_valid || !skd_hlast || !skd_vlast);
628 | // }}}
629 | ////////////////////////////////////////////////////////////////////////
630 | //
631 | // Incoming properties
632 | // {{{
633 | always @(posedge i_clk)
634 | if (!f_past_valid || $past(i_reset))
635 | assume(!s_vid_valid);
636 | else if ($past(s_vid_valid && !s_vid_ready))
637 | begin
638 | assume(s_vid_valid);
639 | assume($stable(s_vid_data));
640 | assume($stable(s_vid_hlast));
641 | assume($stable(s_vid_vlast));
642 | end
643 |
644 | always @(*)
645 | if (s_vid_valid)
646 | assume(s_vid_data != fnvr_pixel);
647 |
648 | // }}}
649 | ////////////////////////////////////////////////////////////////////////
650 | //
651 | // S1 assertions
652 | // {{{
653 | always @(posedge i_clk)
654 | if (!f_past_valid || $past(i_reset))
655 | assert(!s1_valid);
656 | else if ($past(s1_valid && !s1_ready))
657 | begin
658 | assert(s1_valid);
659 | assert($stable(s1_rhash));
660 | assert($stable(s1_ghash));
661 | assert($stable(s1_bhash));
662 | assert($stable(s1_last));
663 | assert($stable(s1_pixel));
664 | end
665 |
666 | always @(*)
667 | if (s1_valid)
668 | assert(s1_pixel != fnvr_pixel);
669 |
670 | always @(*)
671 | begin
672 | f1_rhash = (s1_pixel[23:16] << 1) + s1_pixel[23:16];
673 | f1_ghash = (s1_pixel[15: 8] << 2) + s1_pixel[15: 8];
674 | f1_bhash = (s1_pixel[ 7: 0] << 3) - s1_pixel[ 7: 0];
675 | end
676 |
677 | always @(*)
678 | if (s1_valid)
679 | begin
680 | assert(f1_rhash == s1_rhash);
681 | assert(f1_ghash == s1_ghash);
682 | assert(f1_bhash == s1_bhash);
683 | end
684 |
685 | initial f1_pcount = 0;
686 | always @(posedge i_clk)
687 | if (i_reset)
688 | f1_pcount <= 0;
689 | else if (s1_valid && s1_ready && s1_last)
690 | f1_pcount <= (skd_valid && skd_ready) ? 1:0;
691 | else if (skd_valid && skd_ready)
692 | f1_pcount <= f1_pcount + 1;
693 |
694 | always @(*)
695 | if (f1_pcount == 0)
696 | assert(s1_pixel == 0);
697 |
698 | always @(*)
699 | if (&f1_pcount)
700 | assume(s1_valid && s1_last);
701 | // }}}
702 | ////////////////////////////////////////////////////////////////////////
703 | //
704 | // S2 assertions
705 | // {{{
706 | always @(posedge i_clk)
707 | if (!f_past_valid || $past(i_reset))
708 | begin
709 | assert(!s2_valid);
710 | assert(s2_pixel == 0);
711 | end else if ($past(s2_valid && !s2_ready))
712 | begin
713 | assert(s2_valid);
714 | assert($stable(s2_tbl_index));
715 | assert($stable(s2_last));
716 | assert($stable(s2_gdiff));
717 | assert($stable(s2_pixel));
718 | end
719 |
720 | always @(*)
721 | if (s2_valid)
722 | assert(s2_pixel != fnvr_pixel);
723 |
724 | always @(*)
725 | begin
726 | f2_rhash = (s2_pixel[23:16] << 1) + s2_pixel[23:16];
727 | f2_ghash = (s2_pixel[15: 8] << 2) + s2_pixel[15: 8];
728 | f2_bhash = (s2_pixel[ 7: 0] << 3) - s2_pixel[ 7: 0];
729 |
730 | f2_index = f2_rhash + f2_ghash + f2_bhash + 6'h35;
731 | f2_gdiff = s2_pixel[15: 8] - s3_pixel[15: 8];
732 | end
733 |
734 |
735 | always @(posedge i_clk)
736 | if (s2_valid)
737 | begin
738 | assert(f2_index == s2_tbl_index);
739 | assert(f2_gdiff == s2_gdiff);
740 | end
741 |
742 | initial f2_pcount = 0;
743 | always @(posedge i_clk)
744 | if (i_reset)
745 | f2_pcount <= 0;
746 | else if (s2_valid && s2_ready && s2_last)
747 | f2_pcount <= (s1_valid && s1_ready) ? 1 : 0;
748 | else if (s1_valid && s1_ready)
749 | f2_pcount <= f2_pcount + 1;
750 |
751 | always @(*)
752 | if (s2_valid && !s2_last)
753 | assert(f2_pcount < 32'hffff_ffff);
754 |
755 |
756 | always @(*)
757 | if (f2_pcount == 0)
758 | assert(s2_pixel == 0);
759 |
760 | always @(*)
761 | if (s2_valid && s2_last)
762 | assert(f1_pcount == 0);
763 |
764 | always @(*)
765 | if (!s2_valid || !s2_last)
766 | assert(f1_pcount == f2_pcount + (s1_valid ? 1:0));
767 |
768 | // }}}
769 | ////////////////////////////////////////////////////////////////////////
770 | //
771 | // S3 assertions
772 | // {{{
773 | always @(posedge i_clk)
774 | if (!f_past_valid || $past(i_reset))
775 | begin
776 | assert(!s3_valid);
777 | assert(s3_pixel == 0);
778 | end else if ($past(s3_valid && !s3_ready))
779 | begin
780 | assert(s3_valid);
781 |
782 | // Table lookup
783 | assert($stable(s3_tbl_valid));
784 | assert($stable(s3_tbl_pixel));
785 | assert($stable(s3_tblidx));
786 |
787 | assert($stable(s3_rptvalid));
788 |
789 | assert($stable(s3_pixel));
790 | assert($stable(s3_last));
791 |
792 | assert($stable(s3_rdiff));
793 | assert($stable(s3_gdiff));
794 | assert($stable(s3_bdiff));
795 | assert($stable(s3_rgdiff));
796 | assert($stable(s3_bgdiff));
797 | end
798 |
799 | always @(*)
800 | if (s3_valid)
801 | assert(s3_pixel != fnvr_pixel);
802 |
803 | always @(*)
804 | if (!s3_rptvalid)
805 | assert(s3_repeats == 0);
806 | else
807 | assert(s3_repeats <= 6'h3d);
808 |
809 | always @(*)
810 | begin
811 | f3_rhash = (s3_pixel[23:16] << 1) + s3_pixel[23:16];
812 | f3_ghash = (s3_pixel[15: 8] << 2) + s3_pixel[15: 8];
813 | f3_bhash = (s3_pixel[ 7: 0] << 3) - s3_pixel[ 7: 0];
814 |
815 | f3_index = f3_rhash + f3_ghash + f3_bhash + 6'h35;
816 | f3_gdiff = s3_pixel[15: 8] - s4_pixel[15: 8];
817 |
818 | f3_rdiff = s3_pixel[23:16] - s4_pixel[23:16];
819 | f3_bdiff = s3_pixel[ 7: 0] - s4_pixel[ 7: 0];
820 |
821 | f3_rgdiff = (s3_pixel[23:16] - s4_pixel[23:16]) - s3_gdiff;
822 | f3_bgdiff = (s3_pixel[ 7: 0] - s4_pixel[ 7: 0]) - s3_gdiff;
823 | end
824 |
825 | always @(*)
826 | if (s3_valid)
827 | begin
828 | assert(s3_tblidx == f3_index);
829 | if (!s3_rptvalid)
830 | begin
831 | assert(s3_rdiff == f3_rdiff);
832 | assert(s3_gdiff == f3_gdiff);
833 | assert(s3_bdiff == f3_bdiff);
834 |
835 | assert(s3_rgdiff == f3_rgdiff);
836 | assert(s3_bgdiff == f3_bgdiff);
837 |
838 | assert(s3_pixel != s4_pixel);
839 | end else
840 | assert(s3_pixel == s4_pixel);
841 | end else if (f3_pcount == 0)
842 | assert(s3_pixel == 0);
843 | else
844 | assert(s3_pixel == s4_pixel);
845 |
846 | initial f3_pcount = 0;
847 | always @(posedge i_clk)
848 | if (i_reset)
849 | f3_pcount <= 0;
850 | else if (s3_valid && s3_ready && s3_last)
851 | f3_pcount <= (s2_valid && s2_ready) ? 1 : 0;
852 | else if (s2_valid && s2_ready)
853 | f3_pcount <= f3_pcount + 1;
854 |
855 | always @(*)
856 | if (s3_valid && !s3_last)
857 | assert(f3_pcount < 32'hffff_ffff);
858 |
859 | always @(*)
860 | if (f3_pcount == 0)
861 | assert(s3_pixel == 0);
862 |
863 | always @(*)
864 | if (s3_valid && s3_last)
865 | assert(f2_pcount == 0);
866 |
867 | always @(*)
868 | if (!s3_valid || !s3_last)
869 | assert(f2_pcount == f3_pcount + (s2_valid ? 1:0));
870 |
871 | always @(*)
872 | if (!s3_valid && (!s4_valid || !s4_last))
873 | assert(s3_pixel == s4_pixel);
874 |
875 | // }}}
876 | ////////////////////////////////////////////////////////////////////////
877 | //
878 | // S4 assertions
879 | // {{{
880 | always @(posedge i_clk)
881 | if (!f_past_valid || $past(i_reset))
882 | begin
883 | assert(!s4_valid);
884 | assert(s4_pixel == 0);
885 | end else if ($past(s4_valid && !s4_ready))
886 | begin
887 | assert(s4_valid);
888 | assert($stable(s4_tblset));
889 | assert($stable(s4_rptset));
890 | assert($stable(s4_last));
891 | assert($stable(s4_small));
892 | assert($stable(s4_bigdf));
893 | assert($stable(s4_tblidx));
894 | assert($stable(s4_repeats));
895 | assert($stable(s4_pixel));
896 | assert($stable(s4_rgdiff));
897 | assert($stable(s4_bgdiff));
898 | assert($stable(s4_rdiff));
899 | assert($stable(s4_gdiff));
900 | assert($stable(s4_bdiff));
901 | end
902 |
903 | always @(*)
904 | if (s4_valid)
905 | assert(s4_pixel != fnvr_pixel);
906 |
907 | always @(*)
908 | if (s4_rptset)
909 | begin
910 | assert(s4_repeats <= 6'h3d);
911 | if (s4_valid)
912 | assert(s4_pixel == fm_pixel);
913 | end else if (s4_valid)
914 | assert(s4_pixel != fm_pixel);
915 |
916 | always @(*)
917 | begin
918 | f4_rhash = (s4_pixel[23:16] << 1) + s4_pixel[23:16];
919 | f4_ghash = (s4_pixel[15: 8] << 2) + s4_pixel[15: 8];
920 | f4_bhash = (s4_pixel[ 7: 0] << 3) - s4_pixel[ 7: 0];
921 |
922 | f4_index = f4_rhash + f4_ghash + f4_bhash + 6'h35;
923 | f4_gdiff = s4_pixel[15: 8] - fm_pixel[15: 8];
924 |
925 | f4_rdiff = s4_pixel[23:16] - fm_pixel[23:16];
926 | f4_bdiff = s4_pixel[ 7: 0] - fm_pixel[ 7: 0];
927 |
928 | f4_rgdiff = (s4_pixel[23:16] - fm_pixel[23:16]) - f4_gdiff;
929 | f4_bgdiff = (s4_pixel[ 7: 0] - fm_pixel[ 7: 0]) - f4_gdiff;
930 | end
931 |
932 | always @(*)
933 | if (s4_valid)
934 | begin
935 | if (s4_small && !s4_rptset)
936 | begin
937 | assert(f4_rdiff == { {(6){s4_rdiff[1]}}, s4_rdiff });
938 | assert(f4_gdiff == { {(6){s4_gdiff[1]}},s4_gdiff[1:0]});
939 | assert(f4_bdiff == { {(6){s4_bdiff[1]}}, s4_bdiff });
940 | end
941 |
942 | if (s4_bigdf && !s4_rptset)
943 | begin
944 | assert(f4_rgdiff == { {(4){s4_rgdiff[3]}}, s4_rgdiff });
945 | assert(f4_gdiff == { {(2){s4_gdiff[5] }}, s4_gdiff });
946 | assert(f4_bgdiff == { {(4){s4_bgdiff[3]}}, s4_bgdiff });
947 | end
948 | end
949 |
950 | initial f4_pcount = 0;
951 | always @(posedge i_clk)
952 | if (i_reset)
953 | f4_pcount <= 0;
954 | else if (s4_valid && s4_ready && s4_last)
955 | f4_pcount <= 0;
956 | else if (s3_valid && s3_ready && (!s3_rptvalid || !s3_continue))
957 | f4_pcount <= f4_pcount + 1 + s3_repeats;
958 |
959 | always @(*)
960 | if (!s4_valid && (!m_valid || !m_last))
961 | assert(s4_pixel == fm_pixel);
962 |
963 | always @(*)
964 | if (s4_valid && s4_rptset)
965 | assert(f4_pcount >= s4_repeats+1);
966 |
967 | always @(*)
968 | if (s4_valid && !s4_last)
969 | assert(f4_pcount < 32'hffff_ffff);
970 |
971 | always @(*)
972 | if (f4_pcount == 0)
973 | assert(s4_pixel == 0);
974 |
975 | always @(*)
976 | if (s4_valid && s4_last)
977 | begin
978 | assert(f3_pcount == 0);
979 | end else
980 | assert(f4_pcount <= f3_pcount);
981 |
982 | always @(*)
983 | if (!s4_valid || !s4_last)
984 | assert(f3_pcount == f4_pcount+ ((s3_valid || s3_rptvalid ? 1:0))
985 | + s3_repeats);
986 |
987 | // }}}
988 | ////////////////////////////////////////////////////////////////////////
989 | //
990 | // Final result assertions
991 | // {{{
992 | //
993 | // Rules:
994 | // (AXI Stream rules ...)
995 | // Cannot have two table entries to the same index in a row
996 | // No references to ALPHA
997 | // The decoded pixel must not equal the never pixel
998 | // The decoded pixel must equal the requested pixel
999 | // * 8'hff, then fm_pixel == m_data[23:0]
1000 | // * 2'b11, then fm_pixel must equal table pixel
1001 | // (or table must've been overwritten)
1002 | // (Proven at step 3, not step 4 or m* step)
1003 | // * 2'b00, Last pixel must be the repeating pixel value
1004 | // *!2'b00, Last pixel must be different from the current
1005 | // one
1006 | // * 2'b01, Decoded pixel must match
1007 | // * 2'b10, Decoded pixel must match
1008 | // * (No incoming last, no outgoing last)
1009 | // * # pixels in == # pixels out
1010 |
1011 | always @(posedge i_clk)
1012 | if (!f_past_valid || $past(i_reset))
1013 | assert(!m_valid);
1014 | else if ($past(m_valid && !m_ready))
1015 | begin
1016 | assert(m_valid);
1017 | assert($stable(m_data));
1018 | assert($stable(m_bytes));
1019 | assert($stable(m_last));
1020 | end
1021 |
1022 | initial fm_pixel = 0;
1023 | always @(posedge i_clk)
1024 | if (i_reset)
1025 | fm_pixel <= 0;
1026 | else begin
1027 | if (m_valid && m_ready && m_last)
1028 | fm_pixel <= 0;
1029 | if (s4_valid && s4_ready)
1030 | fm_pixel <= s4_pixel;
1031 | end
1032 |
1033 | always @(*)
1034 | if (m_valid)
1035 | case(m_data[31:30])
1036 | 2'b11: begin
1037 | assert(m_data[31:24] != 8'hff);
1038 | if (m_data[31:24] == 8'hfe)
1039 | begin
1040 | assert(m_bytes == 2'd0);
1041 | assert(m_data[23:0] != fnvr_pixel);
1042 | assert(m_data[23:0] == fm_pixel);
1043 | assert(m_data[23:0] != flst_pixel);
1044 | end else begin
1045 | // Repeated pixel
1046 | assert(m_bytes == 2'd1);
1047 | assert(fm_pixel == flst_pixel);
1048 | end end
1049 | 2'b00: begin
1050 | assert(m_bytes == 2'd1);
1051 | // if (m_data[29:24] == fc_index) assert(fc_valid);
1052 | end
1053 | 2'b01: begin
1054 | assert(m_bytes == 2'd1);
1055 | assert(fm_delta == fm_pixel);
1056 | end
1057 | 2'b10: begin
1058 | assert(m_bytes == 2'd2);
1059 | assert(fm_luna == fm_pixel);
1060 | end
1061 | endcase
1062 |
1063 | always @(*)
1064 | if(m_valid)
1065 | assert(fm_pixel != fnvr_pixel);
1066 |
1067 | always @(*)
1068 | if(m_valid)
1069 | case(m_bytes)
1070 | 2'b00: begin end
1071 | 2'b01: assert(m_data[23:0] == 24'h0);
1072 | 2'b10: assert(m_data[15:0] == 16'h0);
1073 | 2'b11: assert(m_data[ 7:0] == 8'h0);
1074 | endcase
1075 |
1076 | initial fm_pcount = 0;
1077 | always @(posedge i_clk)
1078 | if (i_reset)
1079 | fm_pcount <= 0;
1080 | else if (m_valid && m_ready && m_last)
1081 | fm_pcount <= 0;
1082 | else if (s4_valid && s4_ready)
1083 | begin
1084 | if (s4_rptset)
1085 | fm_pcount <= fm_pcount + s4_repeats + 1;
1086 | else
1087 | fm_pcount <= fm_pcount + 1;
1088 | end
1089 |
1090 | always @(*)
1091 | if (!m_valid || !m_last)
1092 | assert(f4_pcount == fm_pcount + (s4_valid ? 1:0)
1093 | + ((s4_valid && s4_rptset) ? s4_repeats : 0));
1094 |
1095 | initial flst_pixel = 0;
1096 | always @(posedge i_clk)
1097 | if (i_reset || (m_valid && m_ready && m_last))
1098 | flst_pixel <= 0;
1099 | else if (m_valid && m_ready)
1100 | flst_pixel <= fm_pixel;
1101 |
1102 | always @(*)
1103 | if (!m_valid)
1104 | assert(flst_pixel == fm_pixel);
1105 |
1106 | always @(*)
1107 | begin
1108 | fm_delta[23:16] = flst_pixel[23:16] + m_data[29:28] - 2;
1109 | fm_delta[15: 8] = flst_pixel[15: 8] + m_data[27:26] - 2;
1110 | fm_delta[ 7: 0] = flst_pixel[ 7: 0] + m_data[25:24] - 2;
1111 |
1112 | fm_vg = m_data[29:24] - 32;
1113 | fm_luna[23:16] = flst_pixel[23:16] + fm_vg - 8 + m_data[23:20];
1114 | fm_luna[15: 8] = flst_pixel[15: 8] + fm_vg;
1115 | fm_luna[ 7: 0] = flst_pixel[ 7: 0] + fm_vg - 8 + m_data[19:16];
1116 | end
1117 |
1118 | always @(*)
1119 | if (fm_pcount == 0)
1120 | begin
1121 | assert(fm_pixel == 0);
1122 | assert(flst_pixel == 0);
1123 | end
1124 |
1125 | always @(*)
1126 | if (m_valid && !m_last)
1127 | assert(fm_pcount < 32'hffff_ffff);
1128 |
1129 | always @(*)
1130 | if (fnvr_last)
1131 | assume(!s_vid_valid || !s_vid_hlast || !s_vid_vlast);
1132 |
1133 | always @(*)
1134 | if (f_past_valid && fnvr_last)
1135 | begin
1136 | assert(!gbl_last);
1137 | assert(!s1_last);
1138 | assert(!s2_last);
1139 | assert(!s3_last);
1140 | assert(!s4_last);
1141 | assert(!m_last);
1142 | end
1143 |
1144 |
1145 | // }}}
1146 | ////////////////////////////////////////////////////////////////////////
1147 | //
1148 | // Table checking
1149 | // {{{
1150 | reg f3_tbl_valid;
1151 | reg [23:0] f3_tbl_pixel;
1152 |
1153 | initial fc_valid = 0;
1154 | always @(posedge i_clk)
1155 | if (i_reset)
1156 | fc_valid <= 0;
1157 | else if (s2_valid && s2_ready && s2_last)
1158 | fc_valid <= 0;
1159 | else if (s2_valid && s2_ready && s2_tbl_index == fc_index)
1160 | fc_valid <= 1'b1;
1161 |
1162 | always @(*)
1163 | assert(tbl_valid[fc_index] == fc_valid);
1164 |
1165 |
1166 | always @(posedge i_clk)
1167 | if (s2_valid && s2_ready && s2_tbl_index == fc_index)
1168 | fc_pixel <= s2_pixel;
1169 |
1170 | always @(*)
1171 | if (fc_valid)
1172 | assert(tbl_pixel[fc_index] == fc_pixel);
1173 |
1174 | always @(posedge i_clk)
1175 | if (s2_valid && s2_ready && s2_tbl_index == fc_index)
1176 | f3_tbl_valid <= fc_valid;
1177 |
1178 | always @(posedge i_clk)
1179 | if (s2_valid && s2_ready && s2_tbl_index == fc_index)
1180 | f3_tbl_pixel <= fc_pixel;
1181 |
1182 | always @(*)
1183 | if (s3_valid && s3_tblidx == fc_index && !s3_last)
1184 | begin
1185 | assert(fc_valid);
1186 | assert(fc_pixel == s3_pixel);
1187 | assert(f3_tbl_valid == s3_tbl_valid);
1188 | assert(!s3_tbl_valid || f3_tbl_pixel == s3_tbl_pixel);
1189 | end
1190 |
1191 | always @(*)
1192 | if (s4_valid && s4_tblidx == fc_index && !s4_last
1193 | &&(!s3_valid || !s3_last))
1194 | begin
1195 | if (s3_valid && s3_tblidx == fc_index)
1196 | begin
1197 | assert(fc_pixel == s3_pixel);
1198 | end else if (s4_tblset)
1199 | begin
1200 | assert(fc_valid);
1201 | assert(fc_pixel == s4_pixel);
1202 | if (m_valid && s4_tblset)
1203 | begin
1204 | // assert(m_data[31:24] != { 2'b00, fc_index });
1205 | end
1206 | end
1207 | end
1208 |
1209 | always @(*)
1210 | if (m_valid && m_data[31:30] == 2'b00)
1211 | begin
1212 | // Can't have two TBL code words to the same table entry on
1213 | // two consecutive code words--should do repeats instead
1214 | // assert(s4_valid || !s4_tblset || s4_tblidx != m_data[29:24]);
1215 | // if (!s4_valid) assert(fm_pixel == s4_pixel);
1216 | if (!m_last && (!s4_valid || (!s4_last && s4_tblidx != m_data[29:24]))
1217 | && (fc_index == m_data[29:24])
1218 | && (!s3_valid || (!s3_last && s3_tblidx != m_data[29:24] && !s4_last)))
1219 | begin
1220 | assert(fc_valid);
1221 | assert(fc_pixel == fm_pixel);
1222 | end
1223 | end
1224 |
1225 | always @(*)
1226 | if (s3_valid && s3_last)
1227 | assert(tbl_valid == 0);
1228 |
1229 | always @(*)
1230 | if (s4_valid && s4_last)
1231 | assert(tbl_valid == 0);
1232 |
1233 | always @(*)
1234 | if (m_valid && m_last)
1235 | assert(tbl_valid == 0);
1236 |
1237 | // assert($stable(s3_tbl_pixel));
1238 | // }}}
1239 | ////////////////////////////////////////////////////////////////////////
1240 | //
1241 | // Careless assumptions
1242 | // {{{
1243 |
1244 | // always @(*) assume(!s3_rptvalid);
1245 | // }}}
1246 | `endif
1247 | // }}}
1248 | endmodule
1249 |
--------------------------------------------------------------------------------
/rtl/qoi_decoder.v:
--------------------------------------------------------------------------------
1 | ////////////////////////////////////////////////////////////////////////////////
2 | //
3 | // Filename: ./rtl/qoi_decoder.v
4 | // {{{
5 | // Project: Quite OK image compression (QOI) Verilog implementation
6 | //
7 | // Purpose: Top level QOI image processing file. This file is primarily
8 | // a wrapper around qoi_decompress. It's purpose is threefold.
9 | //
10 | // 1. Strips the header from the incoming stream, copying width and height
11 | // information
12 | // 2. Act as a gearbox on the incoming data stream, feeding single pixel
13 | // codewords to the decoder.
14 | // 3. Recovers pixel data from the decoder
15 | // 4. Adds TLAST and TUSER based on the given width and height information
16 | // 5. Recognizes the video trailer, and ends decoding when seen.
17 | //
18 | // Creator: Dan Gisselquist, Ph.D.
19 | // Gisselquist Technology, LLC
20 | //
21 | ////////////////////////////////////////////////////////////////////////////////
22 | // }}}
23 | // Copyright (C) 2024, Gisselquist Technology, LLC
24 | // {{{
25 | // This program is free software (firmware): you can redistribute it and/or
26 | // modify it under the terms of the GNU General Public License as published
27 | // by the Free Software Foundation, either version 3 of the License, or (at
28 | // your option) any later version.
29 | //
30 | // This program is distributed in the hope that it will be useful, but WITHOUT
31 | // ANY WARRANTY; without even the implied warranty of MERCHANTIBILITY or
32 | // FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
33 | // for more details.
34 | //
35 | // You should have received a copy of the GNU General Public License along
36 | // with this program. (It's in the $(ROOT)/doc directory. Run make with no
37 | // target there if the PDF file isn't present.) If not, see
38 | // for a copy.
39 | // }}}
40 | // License: GPL, v3, as defined and found on www.gnu.org,
41 | // {{{
42 | // http://www.gnu.org/licenses/gpl.html
43 | //
44 | ////////////////////////////////////////////////////////////////////////////////
45 | //
46 | `default_nettype none
47 | // }}}
48 | module qoi_decoder #(
49 | // {{{
50 | parameter [0:0] OPT_TUSER_IS_SOF = 1'b0,
51 | parameter DW = 64,
52 | localparam DB = DW/8,
53 | localparam LGDB = $clog2(DB),
54 | localparam LGFRAME = 32,
55 | localparam [LGFRAME-1:0] DEF_WIDTH = 800,
56 | localparam [LGFRAME-1:0] DEF_HEIGHT = 600
57 | // }}}
58 | ) (
59 | // {{{
60 | input wire i_clk, i_reset,
61 | //
62 | input reg i_qvalid,
63 | output wire o_qready,
64 | input reg [DW-1:0] i_qdata,
65 | input reg [LGDB-1:0] i_qbytes,
66 | // qlast is a nice idea, but ... it's redundant. How should
67 | // qlast be handled when there's already a last indicator within
68 | // the data stream as it is?
69 | // input reg i_qlast
70 | //
71 | output wire m_valid,
72 | input wire m_ready,
73 | output wire [23:0] m_data,
74 | output wire m_last, m_user
75 | // }}}
76 | );
77 |
78 | // Local declarations
79 | // {{{
80 | localparam [2:0] DC_SYNC = 0,
81 | DC_WIDTH = 1,
82 | DC_HEIGHT = 2,
83 | DC_FORMAT = 3,
84 | DC_DATA = 4,
85 | DC_TAIL = 5;
86 | localparam SRW = 56 + DW;
87 |
88 | reg [2:0] state;
89 | reg [LGFRAME-1:0] r_width, r_height;
90 |
91 | reg [SRW-1:0] sreg;
92 | reg [$clog2(SRW/8+1)-1:0] sr_nvalid, nxt_step, nxt_nvalid;
93 | wire [LGDB:0] wide_qbytes;
94 | reg eoi_marker, sr_last;
95 | wire sr_valid, sr_ready;
96 |
97 | reg pre_valid;
98 | reg [39:0] pre_data;
99 | wire pre_ready, pre_last;
100 |
101 | reg in_valid, in_last;
102 | reg [39:0] in_data;
103 | wire in_ready;
104 |
105 | wire d_valid, d_ready, d_last;
106 | wire [23:0] d_pixel;
107 |
108 | reg [LGFRAME-1:0] ypos, xpos;
109 | reg m_hlast, m_vlast, m_eof, midframe, lcl_reset;
110 | // }}}
111 | ////////////////////////////////////////////////////////////////////////
112 | //
113 | // Strip off the image header and trailer, grabbing the height and width
114 | // {{{
115 | ////////////////////////////////////////////////////////////////////////
116 | //
117 | // 1. Reset the decompression algorithm. Hold it in reset.
118 | // 2. Search for the SYNC, "qoif".
119 | // Insist that this SYNC be aligned with a 32b word.
120 | // If the image doesn't have it, wait until it shows up.
121 | // 3. Grab the header data (height/width) next.
122 | // 4. Ignore the next two bytes.
123 | // Unpacking only 2 bytes will necessitate gearbox integration here
124 | // 5. For each code word, grab an appropriately sized word
125 | // Count pixels--horizontal and vertical--going into the decoder.
126 | // 6. After HEIGHT * WIDTH - 1 pixels, mark decoder word as *LAST*.
127 | // Ignore everything that might follow.
128 | // 7. Once HLAST+VLAST take place, reset the qoi_decompress module
129 | // 8. Go back to step 2, to search for the SYNC again
130 | //
131 |
132 | // nxt_step
133 | // {{{
134 | always @(*)
135 | begin
136 | nxt_step = 4;
137 | if (state == DC_SYNC)
138 | begin
139 | // if (sreg[DW+32-1:DW] == "qoif")
140 | // step = 4;
141 | nxt_step = 4;
142 | if (sreg[SRW-8-1:SRW-32] == "qoi")
143 | nxt_step = 1;
144 | else if (sreg[SRW-16-1:SRW-32] == "qo")
145 | nxt_step = 2;
146 | else if (sreg[SRW-24-1:SRW-32] == "q")
147 | nxt_step = 3;
148 | else
149 | nxt_step = 4;
150 | end // else if (state == DC_SIZE)
151 | // nxt_step = 4;
152 | else if (state == DC_FORMAT)
153 | nxt_step = 2;
154 | else if (state == DC_DATA)
155 | begin
156 | casez(sreg[SRW-1:SRW-8])
157 | 8'b1111_1110: nxt_step = 4;
158 | 8'b1111_1111: nxt_step = 5;
159 | 8'b10??_????: nxt_step = 2;
160 | default: nxt_step = 1;
161 | endcase
162 | end else
163 | nxt_step = 4;
164 | end
165 | // }}}
166 |
167 | always @(*)
168 | eoi_marker = (sreg[SRW-1:SRW-64] == 64'h01);
169 |
170 | // state
171 | // {{{
172 | always @(posedge i_clk)
173 | if (i_reset)
174 | state <= DC_SYNC;
175 | else if (sr_valid && sr_ready)
176 | case(state)
177 | DC_SYNC: if ((sr_nvalid >= 4) && (sreg[SRW-1:SRW-32] == "qoif"))
178 | state <= DC_WIDTH;
179 | DC_WIDTH: if (sr_nvalid >= 4)
180 | state <= DC_HEIGHT;
181 | DC_HEIGHT: if (sr_nvalid >= 4)
182 | state <= DC_FORMAT;
183 | DC_FORMAT: if (sr_nvalid >= 2)
184 | state <= DC_DATA;
185 | DC_DATA: begin
186 | if(sr_nvalid >= 8 && eoi_marker && (!pre_valid || pre_ready))
187 | state <= DC_TAIL;
188 | // if (i_qvalid && o_qready && i_qlast)
189 | // state <= DC_TAIL;
190 | end
191 | DC_TAIL: if (m_valid && m_ready && m_eof)
192 | state <= DC_SYNC;
193 | default: state <= DC_SYNC;
194 | endcase
195 | // }}}
196 |
197 | // r_width, r_height
198 | // {{{
199 | initial r_width = DEF_WIDTH;
200 | initial r_height = DEF_HEIGHT;
201 | always @(posedge i_clk)
202 | if (i_reset)
203 | begin
204 | r_width <= DEF_WIDTH;
205 | r_height <= DEF_HEIGHT;
206 | end else if (sr_nvalid >= 4)
207 | begin
208 | if (state == DC_WIDTH)
209 | r_width <= sreg[SRW-32 +: LGFRAME];
210 | if (state == DC_HEIGHT)
211 | r_height <= sreg[SRW-32 +: LGFRAME];
212 | end
213 | // }}}
214 |
215 | // sr_nvalid
216 | // {{{
217 | assign wide_qbytes = (i_qbytes == 0) ? DB[LGDB:0] : { 1'b0, i_qbytes };
218 |
219 | always @(*)
220 | case({ (i_qvalid && o_qready), (sr_valid && sr_ready) })
221 | 2'b00: nxt_nvalid = sr_nvalid;
222 | 2'b10: nxt_nvalid = sr_nvalid + wide_qbytes;
223 | 2'b01: nxt_nvalid = sr_nvalid - nxt_step;
224 | 2'b11: nxt_nvalid = sr_nvalid + wide_qbytes - nxt_step;
225 | endcase
226 |
227 | always @(posedge i_clk)
228 | if (i_reset)
229 | sr_nvalid <= 0;
230 | else
231 | sr_nvalid <= nxt_nvalid;
232 | // }}}
233 |
234 | // sr_last
235 | // {{{
236 | always @(posedge i_clk)
237 | if (i_reset || (m_valid && m_ready && m_eof))
238 | sr_last <= 0;
239 | else begin
240 | // if (i_qvalid && i_qready && i_qlast)
241 | // sr_last <= 1'b1;
242 | if (sr_valid && sr_ready && sr_nvalid >= 8 && eoi_marker)
243 | sr_last <= 1'b1;
244 | // if (state != DC_DATA)
245 | // sr_last <= 1'b0;
246 | end
247 | // }}}
248 |
249 | // sreg
250 | // {{{
251 | always @(posedge i_clk)
252 | if (i_reset || sr_last)
253 | sreg <= 0;
254 | else case({ (i_qvalid && o_qready), (sr_valid && sr_ready) })
255 | 2'b00: begin end
256 | 2'b10: sreg <= sreg
257 | | ({ {(SRW-DW){1'b0}}, i_qdata } << ((SRW-DW) - sr_nvalid*8));
258 | 2'b01: sreg <= sreg << (8*nxt_step);
259 | 2'b11: sreg <= (sreg << (8*nxt_step))
260 | | ({ {(SRW-DW){1'b0}}, i_qdata } << (SRW-(8*nxt_nvalid)));
261 | endcase
262 | // }}}
263 |
264 | assign sr_valid = (sr_nvalid >= 8);
265 | assign sr_ready = !pre_valid || pre_ready || (state != DC_DATA);
266 | // Verilator lint_off WIDTH
267 | assign o_qready = (state != DC_TAIL && state != DC_DATA) || (sr_nvalid <= SRW/8-DB);
268 | // Verilator lint_on WIDTH
269 | // }}}
270 | ////////////////////////////////////////////////////////////////////////
271 | //
272 | // The *PRE*-pipeline stage -- used for recognizing EOI
273 | // {{{
274 | always @(posedge i_clk)
275 | if (i_reset)
276 | pre_valid <= 1'b0;
277 | else if (sr_valid && sr_ready)
278 | pre_valid <= (sr_nvalid >= 8)&& (state == DC_DATA);
279 | else if (pre_ready)
280 | pre_valid <= 1'b0;
281 |
282 | always @(posedge i_clk)
283 | if (sr_valid && sr_ready)
284 | pre_data <= sreg[SRW-1:SRW-40];
285 |
286 | assign pre_last = sr_last;
287 |
288 | // }}}
289 | ////////////////////////////////////////////////////////////////////////
290 | //
291 | // The feed stage
292 | // {{{
293 | always @(posedge i_clk)
294 | if (i_reset)
295 | in_valid <= 1'b0;
296 | else if (pre_valid && pre_ready)
297 | in_valid <= pre_valid && (state != DC_TAIL);
298 | else if (in_ready)
299 | in_valid <= 1'b0;
300 |
301 | always @(posedge i_clk)
302 | if (pre_valid && pre_ready)
303 | in_data <= pre_data;
304 |
305 | always @(posedge i_clk)
306 | if (pre_valid && pre_ready)
307 | in_last <= pre_last;
308 |
309 | assign pre_ready = (!in_valid || in_ready)&&(sr_nvalid >= 8);
310 |
311 | // }}}
312 | ////////////////////////////////////////////////////////////////////////
313 | //
314 | // Decompress
315 | // {{{
316 | ////////////////////////////////////////////////////////////////////////
317 | //
318 |
319 | qoi_decompress
320 | u_decompress (
321 | // {{{
322 | .i_clk(i_clk),
323 | .i_reset(i_reset || lcl_reset || (m_valid && m_eof)),
324 | //
325 | .s_valid(in_valid),
326 | .s_ready(in_ready),
327 | .s_data(in_data),
328 | .s_last(in_last),
329 | //
330 | .m_valid(d_valid),
331 | .m_ready(d_ready),
332 | .m_data(d_pixel),
333 | .m_last(d_last)
334 | // }}}
335 | );
336 |
337 | // }}}
338 | ////////////////////////////////////////////////////////////////////////
339 | //
340 | // Add TLAST + TUSER (Either HLAST+VLAST, or HLAST+SOF)
341 | // {{{
342 | always @(posedge i_clk)
343 | if (i_reset)
344 | m_valid <= 1'b0;
345 | else if (!m_valid || m_ready)
346 | m_valid <= d_valid || (lcl_reset && midframe);
347 |
348 | always @(posedge i_clk)
349 | if (i_reset)
350 | lcl_reset <= 1'b0;
351 | else if (m_valid && m_ready && m_eof)
352 | lcl_reset <= 1'b0;
353 | else if (d_valid && d_ready && d_last)
354 | lcl_reset <= 1'b1;
355 |
356 | always @(posedge i_clk)
357 | if (d_valid && d_ready)
358 | m_data <= d_pixel;
359 |
360 | always @(posedge i_clk)
361 | if (i_reset)
362 | begin
363 | xpos <= 0;
364 | ypos <= 0;
365 | midframe <= 1'b0;
366 | m_hlast <= 0;
367 | m_vlast <= 0;
368 | end else if (m_valid && m_ready)
369 | begin
370 | xpos <= xpos + 1;
371 | midframe <= 1'b1;
372 | m_hlast <= (xpos + 2 >= r_width);
373 | if (xpos + 1 >= r_width)
374 | begin
375 | m_hlast <= 1'b0;
376 | xpos <= 0;
377 | m_vlast <= (ypos + 2 >= r_height);
378 | ypos <= ypos + 1;
379 | if (ypos + 1 >= r_height)
380 | begin
381 | m_vlast <= 1'b0;
382 | ypos <= 0;
383 | midframe <= 1'b0;
384 | end
385 | end
386 | end
387 |
388 | assign m_eof = m_hlast && m_vlast;
389 |
390 | generate if (OPT_TUSER_IS_SOF)
391 | begin : GEN_SOF
392 | reg m_sof;
393 | always @(posedge i_clk)
394 | if (i_reset)
395 | m_sof <= 1'b1;
396 | else if (m_valid && m_ready)
397 | m_sof <= m_eof;
398 |
399 | assign m_last = m_hlast;
400 | assign m_user = m_sof;
401 | end else begin : GEN_EOF
402 | assign m_last = m_eof;
403 | assign m_user = m_hlast;
404 | end endgenerate
405 |
406 | assign d_ready = !m_valid || m_ready;
407 | // }}}
408 | endmodule
409 |
--------------------------------------------------------------------------------
/rtl/qoi_decompress.v:
--------------------------------------------------------------------------------
1 | ////////////////////////////////////////////////////////////////////////////////
2 | //
3 | // Filename: ./rtl/qoi_decompress.v
4 | // {{{
5 | // Project: Quite OK image compression (QOI) Verilog implementation
6 | //
7 | // Purpose: Decodes the compressed data within a QOI image. By the time
8 | // we get the data, the header and trailer have already been
9 | // stripped from the image, and the values given to us are QOI code words.
10 | // All QOI code words will have their first byte in the MSB. Not all
11 | // QOI codeword bytes will be valid.
12 | //
13 | // The challenge here is the pipeline--particularly because we have to
14 | // take only a single clock cycle to read from memory (unlike software),
15 | // and we won't immediately know what address to read from. Sure, if this
16 | // is a memory pixel, we'll read from the right address-but how will we
17 | // write pixel values to the right address if we haven't already
18 | // calculated their indexes first? Hence, pipeline scheduling is our
19 | // most complex task.
20 | //
21 | // 1. Start calculating table index: R*3 + G*5 + B*7 + A*11
22 | // 8'hfe: If pixel is known ...
23 | // Code = 0
24 | // Pre-R = R*3
25 | // Pre-G = G*5
26 | // Pre-B = R*7
27 | // Pre-A = (Prior value of A)
28 | // Mark as non-offset
29 | // 8'hff: Same, except ...
30 | // Code = 1
31 | // Pre-A = A*11 = A * 16 - A * 4 - A * 1
32 | // 2'b00: (Keep as index)
33 | // Code = 2
34 | // 2'b01: Mark as offset
35 | // Pre-R = dR*3
36 | // Pre-G = dG*5
37 | // Pre-B = dB*7
38 | // Pre-A = 0
39 | // Code = 3
40 | // 2'b10: Mark as offset
41 | // Pre-R = (dR + dG)*3
42 | // Pre-G = dG*5
43 | // Pre-B = (dB + dG)*7
44 | // Pre-A = 0
45 | // Code = 3
46 | // 2'b11: (Keep as run and length)
47 | // Code = 4
48 | // 2. Calculate the table entry
49 | // If run
50 | // Tbl-Idx = last_index
51 | // else if index
52 | // Tbl-Idx = index
53 | // else if offset
54 | // Tbl-IDX = pre-R + pre-G + pre-B + last_index
55 | // else
56 | // Tbl-IDX = pre-R + pre-G + pre-B
57 | // 3. Table write / lookup
58 | // If run
59 | // (skip)
60 | // (pixel is already valid)
61 | // else if index
62 | // pixel = tbl[index]
63 | // else if offset
64 | // pixel <= pixel + offset
65 | // tbl[index] <= pixel + offset
66 | // else
67 | // tbl[index] <= pipeline_pixel
68 | // 4. Run
69 | // if (run_count > 0)
70 | // run_count <= run_count - 1;
71 | // else if (run)
72 | // run_pixel <= run_pixel;
73 | // run_count <= run_count;
74 | // else
75 | // run_pixel <= pixel;
76 | // run_count <= 0;
77 | //
78 | //
79 | // Creator: Dan Gisselquist, Ph.D.
80 | // Gisselquist Technology, LLC
81 | //
82 | ////////////////////////////////////////////////////////////////////////////////
83 | // }}}
84 | // Copyright (C) 2024, Gisselquist Technology, LLC
85 | // {{{
86 | // This program is free software (firmware): you can redistribute it and/or
87 | // modify it under the terms of the GNU General Public License as published
88 | // by the Free Software Foundation, either version 3 of the License, or (at
89 | // your option) any later version.
90 | //
91 | // This program is distributed in the hope that it will be useful, but WITHOUT
92 | // ANY WARRANTY; without even the implied warranty of MERCHANTIBILITY or
93 | // FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
94 | // for more details.
95 | //
96 | // You should have received a copy of the GNU General Public License along
97 | // with this program. (It's in the $(ROOT)/doc directory. Run make with no
98 | // target there if the PDF file isn't present.) If not, see
99 | // for a copy.
100 | // }}}
101 | // License: GPL, v3, as defined and found on www.gnu.org,
102 | // {{{
103 | // http://www.gnu.org/licenses/gpl.html
104 | //
105 | ////////////////////////////////////////////////////////////////////////////////
106 | //
107 | `default_nettype none
108 | // }}}
109 | module qoi_decompress (
110 | input wire i_clk, i_reset,
111 | // QOI compressed input stream
112 | // {{{
113 | input wire s_valid,
114 | output wire s_ready,
115 | input wire [39:0] s_data,
116 | input wire s_last,
117 | // }}}
118 | // Pixel stream output
119 | // {{{
120 | output wire m_valid,
121 | input wire m_ready,
122 | output wire [23:0] m_data,
123 | // We have no knowledge of height or width here. Hence the
124 | // video last signal only indicates the last pixel in the
125 | // frame, not the last pixel in a line or any other such thing.
126 | // The decoder shell should take care of the rest of the video
127 | // sync signals.
128 | output wire m_last
129 | // }}}
130 | );
131 |
132 | // Local declarations
133 | // {{{
134 | localparam [2:0] C_RGB = 0,
135 | C_RGBA = 1,
136 | C_TABLE = 2,
137 | C_DELTA = 3,
138 | C_REPEAT= 4;
139 |
140 | reg s1_valid, s1_last;
141 | wire s1_ready;
142 | wire [7:0] dr_sum, db_sum; // Red and blue differentials
143 | reg [2:0] s1_code;
144 | reg [31:0] s1_pix;
145 | reg [5:0] s1_prer, s1_preg, s1_preb, s1_prea;
146 |
147 | reg s2_valid, s2_last;
148 | wire s2_ready;
149 | reg [2:0] s2_code;
150 | reg [31:0] s2_pix;
151 | reg [5:0] s2_index, s2_alpha, s2_run;
152 |
153 | reg s3_valid, s3_last;
154 | wire s3_ready;
155 | reg [2:0] s3_code;
156 | reg [31:0] s3_lookup, s3_write_value, s3_raw;
157 | wire [31:0] s3_pixel;
158 | wire [5:0] s3_write_index;
159 | reg [5:0] s3_run;
160 | reg [5:0] s3_index;
161 |
162 | reg [31:0] tbl [0:63];
163 |
164 | reg s4_valid, s4_last;
165 | wire s4_ready;
166 | reg [5:0] s4_count;
167 | reg [31:0] s4_pixel;
168 |
169 | assign s_ready = (!s1_valid || s1_ready);
170 |
171 | // }}}
172 | ////////////////////////////////////////////////////////////////////////
173 | //
174 | // s1
175 | // {{{
176 |
177 | initial s1_valid = 1'b0;
178 | always @(posedge i_clk)
179 | if (i_reset)
180 | { s1_valid, s1_last } <= 0;
181 | else if (!s1_valid || s1_ready)
182 | { s1_valid, s1_last } <= { s_valid, s_last };
183 |
184 | wire [3:0] s_dred, s_dblu;
185 | wire [5:0] s_dgrn;
186 |
187 | assign s_dgrn = s_data[37:32] ^ 6'd32;
188 | assign s_dred = s_data[31:28] ^ 4'd8;
189 | assign s_dblu = s_data[27:24] ^ 4'd8;
190 |
191 | // Red - green
192 | assign dr_sum = { {(4){s_dred[3]}}, s_dred[3:0] }
193 | + { {(2){s_dgrn[5]}}, s_dgrn[5:0] };
194 | // Blue - green
195 | assign db_sum = { {(4){s_dblu[3]}}, s_dblu[3:0] }
196 | + { {(2){s_dgrn[5]}}, s_dgrn[5:0] };
197 |
198 | wire [5:0] s_delta;
199 |
200 | assign s_delta = {
201 | s_data[37:36] ^ 2'b10,
202 | s_data[35:34] ^ 2'b10,
203 | s_data[33:32] ^ 2'b10 };
204 |
205 | always @(posedge i_clk)
206 | if (s_valid && s_ready)
207 | begin
208 | case(s_data[39:38])
209 | 2'b00: begin // Table lookup
210 | // {{{
211 | s1_code <= C_TABLE;
212 | s1_pix <= { s_data[39:32], 24'h0 };
213 | //
214 | s1_prer <= 6'h0;
215 | s1_preg <= 6'h0;
216 | s1_preb <= 6'h0;
217 | s1_prea <= 6'h0;
218 | end
219 | // }}}
220 | 2'b01: begin
221 | s1_code <= C_DELTA;
222 | s1_pix[31:24] <= { {(6){s_delta[5]}}, s_delta[5:4]};
223 | s1_pix[23:16] <= { {(6){s_delta[3]}}, s_delta[3:2]};
224 | s1_pix[15: 8] <= { {(6){s_delta[1]}}, s_delta[1:0]};
225 | // dR * 3
226 | s1_prer <= { {(3){s_delta[5]}}, s_delta[5:4], 1'b0 }
227 | + { {(4){s_delta[5]}}, s_delta[5:4] };
228 | // dG * 5
229 | s1_preg <= { {(2){s_delta[3]}}, s_delta[3:2], 2'b00 }
230 | + { {(4){s_delta[3]}}, s_delta[3:2] };
231 | // dR * 7
232 | s1_preb <= { {(1){s_delta[1]}}, s_delta[1:0], 3'b000 }
233 | - { {(4){s_delta[1]}}, s_delta[1:0] };
234 | // Alpha stays the same
235 | s1_prea <= 0;
236 | end
237 | 2'b10: begin // LUNA
238 | // {{{
239 | s1_code <= C_DELTA;
240 | //
241 | s1_pix[31:24] <= dr_sum;
242 | s1_pix[23:16] <= { {(2){s_dgrn[5]}}, s_dgrn[5:0]};
243 | s1_pix[15: 8] <= db_sum;
244 | s1_pix[ 7: 0] <= 0;
245 | // dR * 3
246 | s1_prer <= { dr_sum[4:0], 1'b0 } + dr_sum[5:0];
247 | // dG * 5
248 | s1_preg <= { s_dgrn[5:0] } + { s_dgrn[3:0], 2'b00 };
249 | s1_preb <= { db_sum[2:0], 3'b0 } - db_sum[5:0];
250 | s1_prea <= 0;
251 | end
252 | // }}}
253 | 2'b11: if (s_data[39:32] == 8'hfe)
254 | begin // RGB
255 | // {{{
256 | s1_code <= C_RGB;
257 | s1_pix <= { s_data[31: 8], 8'h0 };
258 | // R * 3
259 | s1_prer <= { s_data[28:24], 1'b0 } + s_data[29:24];
260 | // G * 5
261 | s1_preg <= { s_data[19:16], 2'b0 } + s_data[21:16];
262 | // B * 7
263 | s1_preb <= { s_data[10: 8], 3'b0 } - s_data[13: 8];
264 | // A * 11 = (255 * 11), but only on the first case
265 | // 1011 0000 0000
266 | // 1111 1111 0101
267 | // --------------
268 | // 1010 1111 0101 -> 11 0101 -> 48+5 = 53
269 | // s1_prea <= 55; // This is an offset
270 | // }}}
271 | end else if (s_data[39:32] == 8'hff)
272 | begin // RGB + Alpha
273 | // {{{
274 | s1_code<= C_RGBA;
275 | s1_pix <= s_data[31: 0];
276 | // R * 3
277 | s1_prer<={ s_data[28:24], 1'b0 }+ s_data[29:24];
278 | // G * 5
279 | s1_preg<={ s_data[19:16], 2'b0 }+ s_data[21:16];
280 | // B * 7
281 | s1_preb<={ s_data[10: 8], 3'b0 }- s_data[13: 8];
282 | // A * 11 = (A << 3) + (A << 1) + A // 1011
283 | s1_prea<={ s_data[ 2: 0], 3'b0 }
284 | +{ s_data[ 4: 0], 1'b0 }+ s_data[ 5: 0];
285 | // }}}
286 | end else begin // (Keep as run and length)
287 | s1_code <= C_REPEAT;
288 | s1_pix <= { s_data[39:32], 24'h0 };
289 | s1_prer <= 0;
290 | s1_preg <= 0;
291 | s1_preb <= 0;
292 | s1_prea <= 0;
293 | end
294 | endcase
295 | end
296 |
297 | assign s1_ready = !s2_valid || s2_ready;
298 | // }}}
299 | ////////////////////////////////////////////////////////////////////////
300 | //
301 | // s2
302 | // {{{
303 |
304 | initial s2_valid = 1'b0;
305 | always @(posedge i_clk)
306 | if (i_reset)
307 | { s2_valid, s2_last } <= 0;
308 | else if (!s2_valid || s2_ready)
309 | { s2_valid, s2_last } <= { s1_valid, s1_last };
310 |
311 | always @(posedge i_clk)
312 | if (i_reset)
313 | s2_alpha <= 6'h35;
314 | else if (s1_valid && s1_ready && s1_code == C_RGBA)
315 | s2_alpha <= s1_prea;
316 | // else if (s3_valid && s3_code == C_TABLE)
317 | // s2_alpha <= s3_lookup[7:0] + (s3_lookup[7:0] << 1)
318 | // + (s3_lookup[7:0] << 3);
319 |
320 | always @(posedge i_clk)
321 | if (i_reset)
322 | s2_pix <= 0;
323 | else if (s1_valid && s1_ready)
324 | begin
325 | if (s1_code == C_TABLE)
326 | s2_pix <= 0;
327 | else if (s1_code != C_REPEAT)
328 | s2_pix <= s1_pix;
329 | end
330 |
331 | always @(posedge i_clk)
332 | if (s1_valid && s1_ready)
333 | begin
334 | s2_code <= s1_code;
335 | s2_run <= (s1_code == C_REPEAT) ? s1_pix[29:24] : 6'h0;
336 | case(s1_code)
337 | C_RGB: s2_index <= s1_prer + s1_preg + s1_preb;// + s2_alpha;
338 | C_RGBA: s2_index <= s1_prer + s1_preg + s1_preb + s1_prea;
339 | C_TABLE: s2_index <= s1_pix[29:24];
340 | C_DELTA: s2_index <= s1_prer + s1_preg + s1_preb;// + s2_index;
341 | C_REPEAT: s2_index <= s2_index;
342 | default: begin end
343 | endcase
344 | end
345 |
346 | assign s2_ready = !s3_valid || s3_ready;
347 | // }}}
348 | ////////////////////////////////////////////////////////////////////////
349 | //
350 | // s3: Table lookup
351 | // {{{
352 |
353 | initial s3_valid = 1'b0;
354 | always @(posedge i_clk)
355 | if (i_reset)
356 | { s3_last, s3_valid } <= 0;
357 | else if (!s3_valid || s3_ready)
358 | { s3_last, s3_valid } <= { s2_last, s2_valid };
359 |
360 | always @(posedge i_clk)
361 | if (s2_valid && s2_ready && s2_code == C_TABLE)
362 | s3_lookup <= tbl[s2_index];
363 |
364 | assign s3_write_index = (s2_code == C_RGB) ? (s2_index + s2_alpha)
365 | :(s2_code < C_TABLE) ? s2_index : (s3_index + s2_index);
366 | always @(*)
367 | begin
368 | s3_write_value = s3_raw;
369 | case(s2_code)
370 | C_RGB: s3_write_value[31:8] = s2_pix[31:8];
371 | C_RGBA: s3_write_value = s2_pix[31:0];
372 | C_TABLE: s3_write_value = s3_pixel; // Could be anything ...
373 | C_DELTA: begin
374 | s3_write_value[31:24]= s2_pix[31:24]+ s3_pixel[31:24];
375 | s3_write_value[23:16]= s2_pix[23:16]+ s3_pixel[23:16];
376 | s3_write_value[15: 8]= s2_pix[15: 8]+ s3_pixel[15: 8];
377 | s3_write_value[ 7: 0]= s3_pixel[7:0];
378 | end
379 | default: begin end // s3_write_value = s2_pix;
380 | endcase
381 | end
382 |
383 | always @(posedge i_clk)
384 | if (s2_valid && s2_ready && (s2_code != C_TABLE && !s2_code[2]))
385 | tbl[s3_write_index] <= s3_write_value;
386 |
387 | always @(posedge i_clk)
388 | begin
389 | if (s2_valid && s2_ready)
390 | begin
391 | s3_code <= s2_code;
392 | if (s2_code == C_TABLE)
393 | s3_index <= s2_index;
394 | else if (s2_code != C_REPEAT)
395 | s3_index <= s3_write_index;
396 |
397 |
398 | if (s2_code == C_REPEAT)
399 | begin
400 | s3_run <= s2_run;
401 | if (s3_code == C_TABLE)
402 | s3_raw <= s3_lookup;
403 | end else begin
404 | s3_run <= 0;
405 | s3_raw <= s3_write_value;
406 | end
407 | end
408 |
409 | if (i_reset)
410 | begin
411 | s3_raw <= 32'h00ff;
412 | s3_code <= C_RGBA;
413 | s3_index <= 6'h35;
414 | end
415 | end
416 |
417 | assign s3_pixel = (s3_code == C_TABLE) ? s3_lookup : s3_raw;
418 | assign s3_ready = !s4_valid || (s4_ready && s4_count == 0);
419 | // }}}
420 | ////////////////////////////////////////////////////////////////////////
421 | //
422 | // s4: Repeats
423 | // {{{
424 |
425 | initial s4_valid = 1'b0;
426 | always @(posedge i_clk)
427 | if (i_reset)
428 | { s4_last, s4_valid } <= 0;
429 | else if (!s4_valid || s4_ready)
430 | begin
431 | { s4_last, s4_valid } <= { s3_last, s3_valid };
432 | // s4_last <= (s3_valid && s3_last && s3_run == 0);
433 | end else if (s4_count > 0)
434 | begin
435 | s4_valid <= !s4_last;
436 | // s4_last <= (s4_count <= 1) && s4_lcl_last;
437 | end
438 |
439 | always @(posedge i_clk)
440 | if (i_reset)
441 | s4_count <= 0;
442 | else if (s3_valid && s3_ready)
443 | begin
444 | if (s3_code != C_REPEAT)
445 | s4_count <= 0;
446 | else
447 | s4_count <= s3_run;
448 | end else if ((!m_valid || m_ready) && s4_count > 0)
449 | s4_count <= s4_count - 1;
450 |
451 | always @(posedge i_clk)
452 | if (s3_valid && s3_ready && s3_code != C_REPEAT)
453 | s4_pixel <= s3_pixel;
454 |
455 | assign s4_ready = (!m_valid || m_ready)&&(s4_count == 0);
456 | // }}}
457 |
458 | assign m_valid = s4_valid;
459 | assign m_data = s4_pixel[31:8];
460 | assign m_last = s4_last && (s4_count == 0);
461 |
462 | // Keep Verilator happy
463 | // {{{
464 | // Verilator lint_off UNUSED
465 | wire unused;
466 | assign unused = &{ 1'b0, s4_pixel[7:0] };
467 | // Verilator lint_on UNUSED
468 | // }}}
469 | ////////////////////////////////////////////////////////////////////////////////
470 | ////////////////////////////////////////////////////////////////////////////////
471 | ////////////////////////////////////////////////////////////////////////////////
472 | // Formal properties
473 | // {{{
474 | ////////////////////////////////////////////////////////////////////////////////
475 | ////////////////////////////////////////////////////////////////////////////////
476 | ////////////////////////////////////////////////////////////////////////////////
477 | `ifdef FORMAL
478 | reg f_past_valid;
479 |
480 | initial f_past_valid = 1'b0;
481 | always @(posedge i_clk)
482 | f_past_valid <= 1'b1;
483 |
484 | always @(*)
485 | if (!f_past_valid)
486 | assume(i_reset);
487 |
488 | ////////////////////////////////////////////////////////////////////////
489 | //
490 | // S_* input properties
491 | // {{{
492 | always @(posedge i_clk)
493 | if (!f_past_valid || $past(i_reset))
494 | assume(!s_valid);
495 | else if ($past(s_valid && !s_ready))
496 | begin
497 | assume(s_valid);
498 | assume($stable(s_data));
499 | assume($stable(s_last));
500 | end
501 |
502 | // No two table look ups to the same element in a row
503 |
504 | // }}}
505 | ////////////////////////////////////////////////////////////////////////
506 | //
507 | // S1
508 | // {{{
509 | reg [39:0] f1_raw;
510 |
511 | always @(posedge i_clk)
512 | if (!f_past_valid || $past(i_reset))
513 | assert(!s1_valid);
514 | else if ($past(s1_valid && !s1_ready))
515 | begin
516 | assert(s1_valid);
517 | assert($stable(s1_code));
518 | assert($stable(s1_last));
519 | assert($stable(s1_pix));
520 | assert($stable(s1_prer));
521 | assert($stable(s1_preg));
522 | assert($stable(s1_preb));
523 | assert($stable(s1_prea));
524 |
525 | assert($stable(f1_raw));
526 | end
527 |
528 | always @(posedge i_clk)
529 | if (s_valid && s_ready)
530 | f1_raw <= s_data;
531 | // }}}
532 | ////////////////////////////////////////////////////////////////////////
533 | //
534 | // S2
535 | // {{{
536 | reg [39:0] f2_raw;
537 |
538 | always @(posedge i_clk)
539 | if (!f_past_valid || $past(i_reset))
540 | assert(!s2_valid);
541 | else if ($past(s2_valid && !s2_ready))
542 | begin
543 | assert(s2_valid);
544 | assert($stable(s2_code));
545 | assert($stable(s2_last));
546 | assert($stable(s2_pix));
547 | assert($stable(s2_index));
548 | assert($stable(s2_alpha));
549 | assert($stable(s2_run));
550 |
551 | assert($stable(f2_raw));
552 | end
553 |
554 | always @(posedge i_clk)
555 | if (s1_valid && s1_ready)
556 | f2_raw <= f1_raw;
557 | // }}}
558 | ////////////////////////////////////////////////////////////////////////
559 | //
560 | // S3
561 | // {{{
562 | reg [39:0] f3_raw;
563 |
564 | always @(posedge i_clk)
565 | if (!f_past_valid || $past(i_reset))
566 | assert(!s3_valid);
567 | else if ($past(s3_valid && !s3_ready))
568 | begin
569 | assert(s3_valid);
570 | assert($stable(s3_code));
571 | assert($stable(s3_last));
572 | assert($stable(s3_pixel));
573 | assert($stable(s3_raw));
574 | assert($stable(s3_run));
575 | assert($stable(s3_index));
576 | assert($stable(s3_lookup));
577 |
578 | assert($stable(f3_raw));
579 | end
580 |
581 | always @(posedge i_clk)
582 | if (s2_valid && s2_ready)
583 | f3_raw <= f2_raw;
584 |
585 | reg [5:0] f2_index, f3_index;
586 |
587 | always @(*)
588 | begin
589 | f2_index = s3_write_value[31:24] + (s3_write_value[31:24]<<1)
590 | + s3_write_value[23:16] + (s3_write_value[23:16]<<2)
591 | - s3_write_value[15: 8] + (s3_write_value[15: 8]<<3);
592 | // + ALPHA * 11
593 | f2_index = f2_index + s3_write_value[7:0]
594 | + (s3_write_value[7:0] << 1)
595 | + (s3_write_value[7:0] << 3);
596 |
597 | f3_index = s3_lookup[31:24] + (s3_lookup[31:24]<<1)
598 | + s3_lookup[23:16] + (s3_lookup[23:16]<<2)
599 | - s3_lookup[15: 8] + (s3_lookup[15: 8]<<3);
600 | // + ALPHA * 11
601 | f3_index = f3_index + s3_lookup[7:0]
602 | + (s3_lookup[7:0] << 1)
603 | + (s3_lookup[7:0] << 3);
604 | end
605 |
606 | always @(*)
607 | if (!i_reset && s2_valid && (s2_code != C_TABLE && s2_code != C_REPEAT))
608 | begin
609 | assert(f2_index == s3_write_index);
610 | end
611 |
612 | always @(*)
613 | if (s3_code == C_TABLE)
614 | begin
615 | assume(f3_index == s3_index);
616 | end
617 | // }}}
618 | ////////////////////////////////////////////////////////////////////////
619 | //
620 | // Never Alpha
621 | // {{{
622 | (* anyconst *) reg fnvr_alpha;
623 |
624 | always @(*)
625 | assume(fnvr_alpha);
626 |
627 | always @(*)
628 | if (!i_reset && fnvr_alpha && s_valid)
629 | assume(s_data[39:32] != 8'hff);
630 |
631 | always @(*)
632 | if (!i_reset && fnvr_alpha && s1_valid)
633 | assert(s1_code != C_RGBA);
634 |
635 | always @(*)
636 | if (!i_reset && fnvr_alpha && s2_valid)
637 | assert(s2_code != C_RGBA);
638 |
639 | always @(*)
640 | if (!i_reset && fnvr_alpha && s2_valid && (s2_code != C_TABLE && s2_code != C_REPEAT))
641 | begin
642 | assert(s3_write_value[7:0] == 8'hff);
643 | end
644 |
645 | always @(*)
646 | if (!i_reset && fnvr_alpha && s3_valid)
647 | begin
648 | assert(s3_code != C_RGBA);
649 | assume(s3_lookup[7:0] == 8'hff);
650 | end
651 |
652 | // }}}
653 | ////////////////////////////////////////////////////////////////////////
654 | //
655 | // "Careless" assumptions
656 | // {{{
657 | always @(*)
658 | if(s_valid)
659 | assume(s_data[39:38] != 2'b10);
660 | // always @(*)
661 | // if(s_valid)
662 | // assume(s_data[39:38] != 2'b01);
663 | always @(*)
664 | if(!i_reset && s1_valid)
665 | assert(f1_raw[39:38] != 2'b10);
666 | always @(*)
667 | if(!i_reset && s2_valid)
668 | assert(f2_raw[39:38] != 2'b10);
669 | always @(*)
670 | if(!i_reset && s3_valid)
671 | assert(f3_raw[39:38] != 2'b10);
672 |
673 | always @(*)
674 | assume(m_ready);
675 |
676 | always @(posedge i_clk)
677 | if (!f_past_valid && !$past(i_reset) && $past(s_valid && s_last))
678 | assume(s_valid);
679 | // }}}
680 |
681 | `endif // FORMAL
682 | // }}}
683 | endmodule
684 |
--------------------------------------------------------------------------------
/rtl/qoi_encoder.v:
--------------------------------------------------------------------------------
1 | ////////////////////////////////////////////////////////////////////////////////
2 | //
3 | // Filename: ./rtl/qoi_encoder.v
4 | // {{{
5 | // Project: Quite OK image compression (QOI) Verilog implementation
6 | //
7 | // Purpose: Top level QOI image processing file. This file is primarily
8 | // a wrapper around qoi_compress. It's purpose is threefold.
9 | // First, it converts from Xilinx's video format (TUSER=SOF, TLAST=HLAST)
10 | // to my video format (TUSER=HLAST, TLAST=VLAST). This process also
11 | // counts the image size. Second, it adds the QOI required header.
12 | // Once the header passes, the image pipe becomes a pass through to the
13 | // end of the image data. The third purpose is then to add the required
14 | // QOI trailer to the image stream.
15 | //
16 | // Creator: Dan Gisselquist, Ph.D.
17 | // Gisselquist Technology, LLC
18 | //
19 | ////////////////////////////////////////////////////////////////////////////////
20 | // }}}
21 | // Copyright (C) 2024, Gisselquist Technology, LLC
22 | // {{{
23 | // This program is free software (firmware): you can redistribute it and/or
24 | // modify it under the terms of the GNU General Public License as published
25 | // by the Free Software Foundation, either version 3 of the License, or (at
26 | // your option) any later version.
27 | //
28 | // This program is distributed in the hope that it will be useful, but WITHOUT
29 | // ANY WARRANTY; without even the implied warranty of MERCHANTIBILITY or
30 | // FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
31 | // for more details.
32 | //
33 | // You should have received a copy of the GNU General Public License along
34 | // with this program. (It's in the $(ROOT)/doc directory. Run make with no
35 | // target there if the PDF file isn't present.) If not, see
36 | // for a copy.
37 | // }}}
38 | // License: GPL, v3, as defined and found on www.gnu.org,
39 | // {{{
40 | // http://www.gnu.org/licenses/gpl.html
41 | //
42 | ////////////////////////////////////////////////////////////////////////////////
43 | //
44 | `default_nettype none
45 | // }}}
46 | module qoi_encoder #(
47 | // {{{
48 | parameter [0:0] OPT_TUSER_IS_SOF = 1'b0,
49 | parameter [0:0] OPT_LOWPOWER = 1'b0,
50 | parameter [15:0] LGFRAME=16,
51 | parameter DW = 64,
52 | localparam DB = DW/8,
53 | localparam LGDB = $clog2(DB)
54 | // }}}
55 | ) (
56 | // {{{
57 | input wire i_clk, i_reset,
58 | //
59 | input wire s_valid,
60 | output wire s_ready,
61 | input wire [23:0] s_data,
62 | input wire s_last, s_user,
63 | //
64 | output reg o_qvalid,
65 | input wire i_qready,
66 | output reg [DW-1:0] o_qdata,
67 | output reg [LGDB-1:0] o_qbytes,
68 | output reg o_qlast
69 | // }}}
70 | );
71 |
72 | // Local declarations
73 | // {{{
74 | // STATE:
75 | // NO_SYNC
76 | // START (Have seen the end of the current line/frame)
77 | // SYNCD (Have seen two end of lines/frames)
78 | localparam [1:0] S_NO_SYNC = 2'b00,
79 | S_START = 2'b01,
80 | S_SYNCD = 2'b10;
81 |
82 | wire s_hlast;
83 | reg [1:0] h_state;
84 | reg [LGFRAME-1:0] h_count, h_width;
85 |
86 | wire syncd, s_vlast;
87 | reg [1:0] v_state;
88 | reg [LGFRAME-1:0] v_count, v_height;
89 |
90 | wire e_valid, e_ready;
91 |
92 | wire enc_valid, enc_ready, enc_last;
93 | wire [31:0] enc_data;
94 | wire [1:0] enc_bytes;
95 |
96 | reg [3:0] frm_state;
97 | reg frm_valid, frm_last;
98 | reg [31:0] frm_data;
99 | reg [1:0] frm_bytes;
100 | wire frm_ready;
101 |
102 |
103 | // }}}
104 | ////////////////////////////////////////////////////////////////////////
105 | //
106 | // Step #1: HSYNC
107 | // {{{
108 | ////////////////////////////////////////////////////////////////////////
109 | //
110 | //
111 |
112 | assign s_hlast = (OPT_TUSER_IS_SOF) ? s_last : s_user;
113 |
114 | initial h_state = S_NO_SYNC;
115 | always @(posedge i_clk)
116 | if (i_reset)
117 | h_state <= S_NO_SYNC;
118 | else if (s_valid && s_ready && s_hlast)
119 | case(h_state)
120 | S_NO_SYNC: h_state <= S_START;
121 | default: h_state <= S_SYNCD;
122 | endcase
123 |
124 | initial { h_count, h_width } = 0;
125 | always @(posedge i_clk)
126 | if (i_reset)
127 | { h_count, h_width } <= 0;
128 | else if (s_valid && s_ready)
129 | begin
130 | if (s_hlast)
131 | begin
132 | h_count <= 0;
133 | h_width <= h_count + 1;
134 | end else
135 | h_count <= h_count + 1;
136 | end
137 |
138 | // }}}
139 | ////////////////////////////////////////////////////////////////////////
140 | //
141 | // Step 2: VSYNC and SOF conversion
142 | // {{{
143 | ////////////////////////////////////////////////////////////////////////
144 | //
145 | //
146 |
147 | generate if (OPT_TUSER_IS_SOF)
148 | begin : GEN_VLAST
149 | // {{{
150 | wire s_sof;
151 | reg r_vlast, r_syncd;
152 |
153 | assign s_sof = s_user;
154 |
155 | always @(posedge i_clk)
156 | if (i_reset)
157 | v_state <= S_NO_SYNC;
158 | else if (s_valid && s_ready && s_sof)
159 | case(v_state)
160 | S_NO_SYNC: v_state <= S_START;
161 | default: v_state <= S_SYNCD;
162 | endcase
163 |
164 | initial { v_count, v_height } = 0;
165 | always @(posedge i_clk)
166 | if (i_reset)
167 | { v_count, v_height } <= 0;
168 | else if (s_valid && s_ready)
169 | begin
170 | if (s_sof)
171 | begin
172 | v_count <= 0;
173 | v_height <= v_count;
174 | end else if (s_hlast)
175 | v_count <= v_count + 1;
176 | end
177 |
178 | initial r_vlast = 0;
179 | always @(posedge i_clk)
180 | if (i_reset)
181 | r_vlast <= 0;
182 | else if (s_valid && s_ready && s_hlast)
183 | begin
184 | r_vlast <= (v_count + 2 >= v_height);
185 | if (v_state != S_SYNCD)
186 | r_vlast <= 1'b0;
187 | else if (r_vlast && s_hlast)
188 | r_vlast <= 1'b0;
189 | end
190 |
191 | assign s_vlast = r_vlast;
192 |
193 | always @(*)
194 | begin
195 | r_syncd = (v_state == S_SYNCD);
196 | if (v_state == S_START && s_valid && s_sof)
197 | r_syncd = 1'b1;
198 |
199 | if (h_state != S_SYNCD)
200 | r_syncd = 1'b0;
201 | end
202 |
203 | assign syncd = r_syncd;
204 | // }}}
205 | end else begin : GEN_SIZES
206 | // {{{
207 | // No conversion required
208 | assign s_vlast = s_last;
209 |
210 | // Still need to count the number of lines though ...
211 | always @(posedge i_clk)
212 | if (i_reset)
213 | v_state <= S_START;
214 | else if (s_valid && s_ready && s_vlast && s_hlast)
215 | case(v_state)
216 | S_NO_SYNC: v_state <= S_START;
217 | default: v_state <= S_SYNCD;
218 | endcase
219 |
220 | initial { v_count, v_height } = 0;
221 | always @(posedge i_clk)
222 | if (i_reset)
223 | { v_count, v_height } <= 0;
224 | else if (s_valid && s_ready)
225 | begin
226 | if (s_hlast && s_vlast)
227 | begin
228 | v_count <= 0;
229 | v_height <= v_count + 1;
230 | end else if (s_hlast)
231 | v_count <= v_count + 1;
232 | end
233 |
234 | assign syncd = (h_state == S_SYNCD) && (v_state == S_SYNCD);
235 | // }}}
236 | end endgenerate
237 |
238 | // }}}
239 | ////////////////////////////////////////////////////////////////////////
240 | //
241 | // Step 3: Image encoder
242 | // {{{
243 | ////////////////////////////////////////////////////////////////////////
244 | //
245 | //
246 |
247 | assign e_valid = syncd && s_valid;
248 | assign s_ready = !syncd || e_ready;
249 |
250 | `ifdef FORMAL
251 | (* anyseq *) reg f_ready, f_last, f_valid;
252 | (* anyseq *) reg [31:0] f_data;
253 | (* anyseq *) reg [1:0] f_bytes;
254 |
255 | assign e_ready = f_ready;
256 | assign enc_valid = f_valid;
257 | assign enc_data = f_data;
258 | assign enc_bytes = f_bytes;
259 | assign enc_last = f_last;
260 | `else
261 | qoi_compress
262 | u_compress (
263 | .i_clk(i_clk), .i_reset(i_reset),
264 | //
265 | .s_vid_valid(e_valid), .s_vid_ready(e_ready),
266 | .s_vid_data(s_data),
267 | .s_vid_hlast(s_hlast), .s_vid_vlast(s_vlast),
268 | //
269 | .m_valid(enc_valid), .m_ready(enc_ready),
270 | .m_data( enc_data), .m_bytes(enc_bytes),
271 | .m_last( enc_last)
272 | );
273 | `endif
274 |
275 | assign enc_ready = (frm_state == FRM_DATA)&&(!frm_valid || frm_ready);
276 |
277 | // }}}
278 | ////////////////////////////////////////////////////////////////////////
279 | //
280 | // Step 4: state machine: header and trailer
281 | // {{{
282 |
283 | localparam [3:0] FRM_IDLE = 4'h0,
284 | FRM_START = 4'h1,
285 | FRM_HDRMAGIC = 4'h2,
286 | FRM_HDRWIDTH = 4'h3,
287 | FRM_HDRHEIGHT = 4'h4,
288 | FRM_HDRFORMAT = 4'h5,
289 | FRM_DATA = 4'h6,
290 | FRM_TRAILER = 4'h7,
291 | FRM_LAST = 4'h8;
292 |
293 | always @(posedge i_clk)
294 | if (i_reset || !syncd)
295 | begin
296 | frm_state <= FRM_IDLE;
297 | frm_valid <= 1'b0;
298 | frm_data <= "qoif";
299 | frm_bytes <= 2'b00;
300 | frm_last <= 1'b0;
301 | end else if (!frm_valid || frm_ready)
302 | case(frm_state)
303 | FRM_IDLE: begin
304 | if (syncd && s_valid)
305 | frm_state <= FRM_START;
306 | frm_valid <= 1'b0;
307 | frm_data <= "qoif";
308 | frm_bytes <= 2'b00;
309 | frm_last <= 1'b0;
310 | end
311 | FRM_START: begin
312 | frm_state <= FRM_HDRMAGIC;
313 | frm_valid <= 1'b0;
314 | frm_data <= "qoif";
315 | frm_bytes <= 2'b00;
316 | frm_last <= 1'b0;
317 | end
318 | FRM_HDRMAGIC: begin
319 | if (!o_qvalid && !sr_last)
320 | begin
321 | frm_state <= FRM_HDRWIDTH;
322 | frm_valid <= 1'b1;
323 | frm_data <= "qoif";
324 | frm_bytes <= 2'b00;
325 | frm_last <= 1'b0;
326 | end end
327 | FRM_HDRWIDTH: begin
328 | frm_state <= FRM_HDRHEIGHT;
329 | frm_valid <= 1'b1;
330 | frm_data <= { {(32-LGFRAME){1'b0}}, h_width };
331 | frm_bytes <= 2'b00;
332 | frm_last <= 1'b0;
333 | end
334 | FRM_HDRHEIGHT: begin
335 | frm_state <= FRM_HDRFORMAT;
336 | frm_valid <= 1'b1;
337 | frm_data <= { {(32-LGFRAME){1'b0}}, v_height };
338 | frm_bytes <= 2'b00;
339 | frm_last <= 1'b0;
340 | end
341 | FRM_HDRFORMAT: begin
342 | frm_state <= FRM_DATA;
343 | frm_valid <= 1'b1;
344 | frm_data <= { 8'd3, 8'd1, 16'h0 };
345 | frm_bytes <= 2'b10;
346 | frm_last <= 1'b0;
347 | end
348 | FRM_DATA: begin
349 | if (enc_valid && enc_last)
350 | frm_state <= FRM_TRAILER;
351 | frm_valid <= enc_valid;
352 | case(enc_bytes)
353 | 2'b00: frm_data <= enc_data;
354 | 2'b01: frm_data <= { enc_data[31:24], 24'h0 };
355 | 2'b10: frm_data <= { enc_data[31:16], 16'h0 };
356 | 2'b11: frm_data <= { enc_data[31: 8], 8'h0 };
357 | endcase
358 | frm_bytes <= enc_bytes;
359 | frm_last <= 1'b0;
360 | end
361 | FRM_TRAILER: begin
362 | frm_state <= FRM_LAST;
363 | frm_valid <= 1'b1;
364 | frm_data <= 32'h0;
365 | frm_bytes <= 2'b00;
366 | frm_last <= 1'b0;
367 | end
368 | FRM_LAST: begin
369 | frm_state <= FRM_IDLE;
370 | frm_valid <= 1'b1;
371 | frm_data <= 32'h01;
372 | frm_bytes <= 2'b00;
373 | frm_last <= 1'b1;
374 | end
375 | default: begin
376 | frm_state <= FRM_IDLE;
377 | frm_valid <= 1'b0;
378 | frm_data <= 32'b0;
379 | frm_bytes <= 2'b0;
380 | frm_last <= 1'b0;
381 | end
382 | endcase
383 |
384 | // Verilator lint_off WIDTH
385 | assign frm_ready = ((!o_qvalid || i_qready)&&sr_fill <= DB)||(sr_fill < DB && !sr_last);
386 | // Verilator lint_on WIDTH
387 | // }}}
388 | ////////////////////////////////////////////////////////////////////////
389 | //
390 | // Step 5: Stream packing
391 | // {{{
392 | ////////////////////////////////////////////////////////////////////////
393 | //
394 | //
395 |
396 | reg [DW+32-1:0] sreg, new_data;
397 | reg [$clog2(DW+32)-3:0] sr_fill, new_fill;
398 | reg sr_last, fl_last, flush;
399 |
400 | always @(*)
401 | begin
402 | new_fill = sr_fill;
403 | // Verilator lint_off WIDTH
404 | if (frm_valid && frm_ready)
405 | begin
406 | if (frm_bytes == 0)
407 | new_fill = new_fill + 4;
408 | else
409 | new_fill = new_fill + frm_bytes;
410 | end
411 |
412 | fl_last = sr_last;
413 | if (frm_valid && frm_ready && !sr_last
414 | && (new_fill <= DB))
415 | fl_last = frm_last;
416 |
417 | flush = sr_last || frm_last;
418 | if (sr_fill >= DW/8)
419 | flush = 1'b1;
420 | if (frm_valid && frm_ready && (new_fill >= DB))
421 | flush = 1'b1;
422 |
423 | new_data = sreg| ({{(DW){1'b0}}, frm_data}
424 | << (DW - (sr_fill*8)));
425 | // Verilator lint_on WIDTH
426 | end
427 |
428 | initial o_qvalid = 0;
429 | initial sr_fill = 0;
430 | always @(posedge i_clk)
431 | if (i_reset)
432 | begin
433 | sr_fill <= 0;
434 | o_qvalid <= 1'b0;
435 | end else if ((!o_qvalid || i_qready) && flush)
436 | begin
437 | o_qvalid <= 1'b1;
438 | // Verilator lint_off WIDTH
439 | sr_fill <= new_fill - DB;
440 | // Verilator lint_on WIDTH
441 | if (sr_last)
442 | sr_fill <= (frm_valid) ? new_fill : 0;
443 | else if (fl_last)
444 | sr_fill <= 0;
445 | end else begin
446 | if (i_qready)
447 | o_qvalid <= 1'b0;
448 | if (frm_valid && frm_ready)
449 | sr_fill <= new_fill;
450 | end
451 |
452 | always @(posedge i_clk)
453 | if (i_reset)
454 | sreg <= 0;
455 | else if ((!o_qvalid || i_qready) && flush)
456 | begin
457 | if (sr_last)
458 | sreg <= (frm_valid) ? { frm_data, {(DW){1'b0}} } : {(DW+32){1'b0}};
459 | else if (fl_last)
460 | sreg <= 0;
461 | else if (frm_valid)
462 | sreg <= { new_data[31:0], {(DW){1'b0}} };
463 | else
464 | sreg <= { sreg[31:0], {(DW){1'b0}} };
465 | end else if (frm_valid && frm_ready)
466 | sreg <= new_data;
467 |
468 | always @(posedge i_clk)
469 | if ((!o_qvalid || i_qready)&&(!OPT_LOWPOWER || flush))
470 | o_qdata <= new_data[DW+31:32];
471 |
472 | always @(posedge i_clk)
473 | if ((!o_qvalid || i_qready)&&(!OPT_LOWPOWER || flush))
474 | begin
475 | // Verilator lint_off WIDTH
476 | if (sr_last)
477 | o_qbytes <= sr_fill[LGDB-1:0];
478 | else if (new_fill >= DB)
479 | o_qbytes <= 0;
480 | else
481 | o_qbytes <= new_fill;
482 | // Verilator lint_on WIDTH
483 | end
484 |
485 | always @(posedge i_clk)
486 | if (i_reset || !syncd)
487 | sr_last <= 1'b0;
488 | else if ((!o_qvalid || i_qready) && flush)
489 | sr_last <= frm_valid && frm_ready && frm_last && !fl_last;
490 | else if (frm_valid && frm_ready)
491 | sr_last <= frm_last;
492 |
493 | always @(posedge i_clk)
494 | if (!o_qvalid || i_qready)
495 | o_qlast <= fl_last;
496 |
497 | // }}}
498 | ////////////////////////////////////////////////////////////////////////////////
499 | ////////////////////////////////////////////////////////////////////////////////
500 | ////////////////////////////////////////////////////////////////////////////////
501 | //
502 | // Formal properties
503 | // {{{
504 | ////////////////////////////////////////////////////////////////////////////////
505 | `ifdef FORMAL
506 | reg f_past_valid;
507 | (* anyconst *) reg [LGFRAME-1:0] f_width, f_height;
508 | reg [LGFRAME-1:0] fs_xpos, fs_ypos;
509 | reg f_known_height, fs_hlast, fs_vlast, fs_sof;
510 |
511 | initial f_past_valid = 1'b0;
512 | always @(posedge i_clk)
513 | f_past_valid <= 1'b1;
514 |
515 | always @(*)
516 | if (!f_past_valid)
517 | assume(i_reset);
518 | ////////////////////////////////////////////////////////////////////////
519 | //
520 | // (Video) Stream properties
521 | // {{{
522 | always @(posedge i_clk)
523 | if (!f_past_valid || $past(i_reset))
524 | assume(!s_valid);
525 | else if ($past(s_valid && !s_ready))
526 | begin
527 | assume(s_valid);
528 | assume($stable(s_data));
529 | assume($stable(s_last));
530 | assume($stable(s_user));
531 | end
532 |
533 | always @(posedge i_clk)
534 | if (!f_past_valid || $past(i_reset))
535 | assume(!enc_valid);
536 | else if ($past(enc_valid && !enc_ready))
537 | begin
538 | assume(enc_valid);
539 | assume($stable(enc_data));
540 | assume($stable(enc_bytes));
541 | assume($stable(enc_last));
542 | end
543 |
544 | faxivideo #(
545 | .LGDIM(LGFRAME),
546 | .OPT_TUSER_IS_SOF(OPT_TUSER_IS_SOF)
547 | ) fvid (
548 | // {{{
549 | .i_clk(i_clk), .i_reset_n(!i_reset),
550 | //
551 | .S_VID_TVALID(s_valid),
552 | .S_VID_TREADY(s_ready),
553 | .S_VID_TDATA(s_data),
554 | .S_VID_TLAST(s_last),
555 | .S_VID_TUSER(s_user),
556 | //
557 | .i_width(f_width), .i_height(f_height),
558 | .o_xpos(fs_xpos), .o_ypos(fs_ypos),
559 | .f_known_height(f_known_height),
560 | .o_hlast(fs_hlast), .o_vlast(fs_vlast), .o_sof(fs_sof)
561 | // }}}
562 | );
563 |
564 | always @(*)
565 | begin
566 | assume(fs_xpos < f_width);
567 | assume(fs_ypos < f_height);
568 | end
569 |
570 | always @(*)
571 | if (!i_reset && s_valid)
572 | begin
573 | if (OPT_TUSER_IS_SOF)
574 | begin
575 | assume( s_last == fs_hlast);
576 | assume( s_user == fs_sof);
577 | end else begin
578 | assume( s_last == (fs_vlast && fs_hlast));
579 | assume( s_user == fs_hlast);
580 | end
581 | end
582 |
583 | always @(posedge i_clk)
584 | if (!i_reset)
585 | begin
586 | assert(h_state != 2'b11);
587 | if (h_state != S_NO_SYNC)
588 | assert(h_count == fs_xpos);
589 | if (h_state == S_SYNCD)
590 | assert(h_width == f_width);
591 | assert(h_count <= fs_xpos);
592 | end
593 |
594 | always @(posedge i_clk)
595 | if (!i_reset)
596 | begin
597 | assert(v_state != 2'b11);
598 | // if (v_state != S_NO_SYNC) assert(h_state != S_NO_SYNC);
599 | // if (v_state == S_SYNCD) assert(h_state == S_SYNCD);
600 | if (OPT_TUSER_IS_SOF)
601 | begin
602 | if (v_state != S_NO_SYNC)
603 | begin
604 | if (!fs_sof)
605 | assert(v_count == fs_ypos);
606 | else
607 | assert(v_count == f_height);
608 | end
609 | if (v_state == S_SYNCD)
610 | assert(v_height == f_height);
611 | if (v_state == S_SYNCD)
612 | assert(s_vlast == (fs_ypos +1 >= f_height));
613 | if (v_count > 0)
614 | assert(h_state != S_NO_SYNC);
615 | if (h_count == 0 && v_state == S_START)
616 | assert(h_state != S_NO_SYNC);
617 | if (!fs_sof)
618 | assert(v_count == fs_ypos);
619 | end else begin
620 | if (v_state != S_NO_SYNC)
621 | assert(v_count == fs_ypos);
622 | if (v_state == S_SYNCD)
623 | assert(v_height == f_height);
624 | end
625 | end
626 |
627 | always @(posedge i_clk)
628 | if (!i_reset && syncd && s_valid)
629 | begin
630 | assert(s_hlast == fs_hlast);
631 | assert(!s_hlast || s_vlast == fs_vlast);
632 | end
633 |
634 | always @(posedge i_clk)
635 | if (!i_reset && syncd && OPT_TUSER_IS_SOF)
636 | assert(s_vlast == (fs_ypos+1 >= f_height));
637 |
638 | always @(posedge i_clk)
639 | if (!i_reset && OPT_TUSER_IS_SOF && v_state != S_SYNCD)
640 | assert(!s_vlast);
641 |
642 | /*
643 | always @(posedge i_clk)
644 | if (!i_reset)
645 | begin
646 | if (v_height != 0 || f_known_height)
647 | assert(v_height == f_height);
648 | assert(v_count == fs_ypos);
649 | end
650 | */
651 | ////////////////////////////////////////////////////////////////////////
652 | //
653 | // Encoder stage properties
654 | // {{{
655 | reg [31:0] enc_count;
656 |
657 | initial enc_count = 0;
658 | always @(posedge i_clk)
659 | if (i_reset)
660 | enc_count <= 0;
661 | else if (enc_valid && enc_ready)
662 | begin
663 | if (enc_last)
664 | enc_count <= 0;
665 | else if (enc_bytes == 0)
666 | enc_count <= enc_count + 4;
667 | else
668 | enc_count <= enc_count + enc_bytes;
669 | end
670 |
671 | always @(posedge i_clk)
672 | if (!i_reset && enc_count != 0)
673 | assert(frm_state == FRM_DATA);
674 |
675 | // }}}
676 | ////////////////////////////////////////////////////////////////////////
677 | //
678 | // Framing stage properties
679 | // {{{
680 | reg [31:0] frm_count;
681 |
682 | initial frm_count = 0;
683 | always @(posedge i_clk)
684 | if (i_reset)
685 | frm_count <= 0;
686 | else if (frm_valid && frm_ready)
687 | begin
688 | if (frm_last)
689 | frm_count <= 0;
690 | else if (frm_bytes == 0)
691 | frm_count <= frm_count + 4;
692 | else
693 | frm_count <= frm_count + frm_bytes;
694 | end
695 |
696 | always @(*)
697 | if (!i_reset && frm_valid)
698 | case(frm_bytes)
699 | 2'b00: begin end
700 | 2'b01: assert(frm_data[23:0] == 24'h0);
701 | 2'b10: assert(frm_data[15:0] == 16'h0);
702 | 2'b11: assert(frm_data[ 7:0] == 24'h0);
703 | default: begin end
704 | endcase
705 |
706 | always @(*)
707 | if (!i_reset)
708 | begin
709 | if (frm_state != FRM_DATA)
710 | assert(enc_count == 0);
711 | else begin
712 | assert(enc_count + 14 == frm_count
713 | + (frm_valid ? (frm_bytes + (frm_bytes == 0 ? 4:0)) : 0));
714 | end
715 | end
716 |
717 | // }}}
718 | ////////////////////////////////////////////////////////////////////////
719 | //
720 | // Shift register
721 | // {{{
722 |
723 | always @(posedge i_clk)
724 | if (!i_reset && (!o_qvalid || !o_qlast) && !sr_last)
725 | assert(frm_count == fq_count + sr_fill + (o_qvalid ? 8:0));
726 |
727 | always @(posedge i_clk)
728 | if (!i_reset && !frm_valid)
729 | assert(!frm_last);
730 |
731 | always @(posedge i_clk)
732 | if (!i_reset && sr_last)
733 | begin
734 | assert(sr_fill > 0);
735 | assert(!o_qvalid || !o_qlast);
736 | end
737 |
738 | always @(posedge i_clk)
739 | if (!i_reset && o_qvalid && o_qlast)
740 | begin
741 | assert(!sr_last);
742 | assert(sr_fill == 0);
743 | assert(!frm_valid);
744 | assert(frm_state < FRM_HDRWIDTH);
745 | end
746 |
747 | always @(posedge i_clk)
748 | if (!i_reset && (sr_last || (o_qvalid && o_qlast)))
749 | begin
750 | case(frm_state)
751 | FRM_IDLE: assert(!frm_valid && frm_count == 0);
752 | FRM_START: assert(frm_count == 0);
753 | FRM_HDRMAGIC: assert(frm_count == 0);
754 | FRM_HDRWIDTH: begin end
755 | FRM_HDRHEIGHT: assert(frm_state == FRM_HDRHEIGHT && frm_count == 8 && !sr_last);
756 | default: assert(0);
757 | endcase
758 | assert(!frm_valid);
759 | assert(frm_count == 0);
760 | end
761 |
762 | always @(posedge i_clk)
763 | if (!i_reset) case(frm_state)
764 | FRM_IDLE: if (frm_valid) assert(frm_last && frm_bytes == 0); else assert(frm_count==0);
765 | FRM_START: assert(syncd && !frm_valid && frm_count == 0);
766 | FRM_HDRMAGIC: assert(syncd && !frm_valid && frm_count == 0 && frm_bytes==0);
767 | FRM_HDRWIDTH: assert(!o_qvalid && sr_fill == 0 && syncd && frm_valid && frm_count == 0 && frm_bytes==0);
768 | FRM_HDRHEIGHT: assert(!o_qvalid && syncd && frm_valid && frm_count == 4 && frm_bytes==0 && !sr_last);
769 | FRM_HDRFORMAT: assert((!o_qvalid || !o_qlast) && syncd && frm_valid && frm_count == 8 && !sr_last);
770 | FRM_DATA: assert(syncd && !sr_last && !frm_last);
771 | FRM_TRAILER: assert(syncd && frm_valid && !frm_last && !sr_last);
772 | FRM_LAST: assert(syncd && frm_valid && !frm_last && frm_bytes==0 && !sr_last);
773 | default: assert(0);
774 | endcase
775 |
776 | // }}}
777 | ////////////////////////////////////////////////////////////////////////
778 | //
779 | // (Compressed) Stream properties
780 | // {{{
781 | reg [31:0] fq_count;
782 |
783 | always @(posedge i_clk)
784 | if (!f_past_valid || $past(i_reset))
785 | assert(!o_qvalid);
786 | else if ($past(o_qvalid && !i_qready))
787 | begin
788 | assert(o_qvalid);
789 | assert($stable(o_qdata));
790 | assert($stable(o_qbytes));
791 | assert($stable(o_qlast));
792 | end
793 |
794 | always @(posedge i_clk)
795 | if (f_past_valid && !$past(i_reset) && o_qvalid)
796 | assert(o_qlast || o_qbytes == 0);
797 |
798 | initial fq_count = 0;
799 | always @(posedge i_clk)
800 | if (i_reset)
801 | fq_count <= 0;
802 | else if (o_qvalid && i_qready)
803 | begin
804 | if (o_qlast)
805 | fq_count <= 0;
806 | else
807 | fq_count <= fq_count + DW/8;
808 | end
809 |
810 | always @(*)
811 | assume(fq_count < 32'hef00_0000);
812 | // }}}
813 | ////////////////////////////////////////////////////////////////////////
814 | //
815 | // Contract byte
816 | // {{{
817 |
818 | (* anyconst *) reg [31:0] fc_index;
819 | (* anyconst *) reg [7:0] fc_byte;
820 |
821 | reg [31:0] fenc_index, fsr_count;
822 | reg [7:0] fenc_byte;
823 | reg [31:0] enc_wide, frm_wide;
824 | reg [DW-1:0] fq_wide;
825 | reg [DW+32-1:0] fsr_empty, fsr_wide;
826 |
827 | always @(*)
828 | assume(fc_index >= 12+2);
829 |
830 | always @(*)
831 | fenc_index = fc_index - 14;
832 |
833 | always @(*)
834 | begin
835 | enc_wide = enc_data << (8*(fenc_index - enc_count));
836 | fenc_byte = enc_wide[31:24];
837 |
838 | fsr_count = frm_count - sr_fill;
839 |
840 | frm_wide = frm_data << (8*(fc_index - frm_count));
841 | fsr_wide = sreg << (8*(fc_index - fsr_count));
842 | fq_wide = o_qdata << (8*(fc_index - fq_count));
843 | end
844 |
845 | always @(*)
846 | if (!i_reset && enc_valid && (enc_count < fenc_index))
847 | assume(!enc_last);
848 |
849 | always @(*)
850 | if (!i_reset && enc_count + (enc_valid ? 4:0) < fenc_index)
851 | assume(!enc_last);
852 |
853 | always @(*)
854 | if (!i_reset && enc_valid && (enc_count <= fenc_index)
855 | &&((enc_bytes == 0 && enc_count+ 4 > fenc_index)
856 | || (enc_bytes != 0 && enc_count+ enc_bytes > fenc_index)))
857 | begin
858 | assume(fenc_byte == fc_byte);
859 | assume(!enc_last);
860 | end
861 |
862 | always @(*)
863 | if (!i_reset && frm_state == FRM_DATA)
864 | begin
865 | assert(frm_count >= 12);
866 | if (frm_count < 14)
867 | begin
868 | assert(frm_valid);
869 | assert(frm_count == 12);
870 | assert(frm_bytes == 2);
871 | assert(enc_count == 0);
872 | end
873 | end
874 |
875 | always @(*)
876 | if (!i_reset && frm_state > FRM_DATA)
877 | assert(frm_count > fc_index);
878 |
879 | always @(*)
880 | if (!i_reset && frm_valid && (frm_count <= fc_index)
881 | &&((frm_bytes == 0 && fc_index < frm_count+ 4)
882 | || (frm_bytes != 0 && fc_index < frm_count+ frm_bytes)))
883 | begin
884 | assert(frm_wide[31:24] == fc_byte);
885 | end
886 |
887 | always @(*)
888 | if (!i_reset && !sr_last && sr_fill > 0 && (fsr_count <= fc_index)
889 | &&(fc_index < fsr_count + sr_fill))
890 | begin
891 | assert(fsr_wide[DW+32-1:DW+24] == fc_byte);
892 | end
893 |
894 | always @(*)
895 | if (!i_reset && o_qvalid && !o_qlast && (fq_count <= fc_index)
896 | &&((o_qbytes == 0 && fc_index < fq_count+ 4)
897 | || (o_qbytes != 0 && fc_index < fq_count+ o_qbytes)))
898 | begin
899 | assert(fq_wide[DW-1:DW-8] == fc_byte);
900 | end
901 |
902 | always @(*)
903 | fsr_empty = sreg << (sr_fill * 8);;
904 |
905 | always @(*)
906 | if(!i_reset)
907 | begin
908 | assert(sr_fill <= (DW+32)/8);
909 | assert(fsr_empty == 0);
910 | end
911 | // }}}
912 | ////////////////////////////////////////////////////////////////////////
913 | //
914 | // Cover checks
915 | // {{{
916 |
917 | always @(*)
918 | if (!i_reset && o_qvalid)
919 | begin
920 | cover(fq_count > 0);
921 | cover(fq_count > 8);
922 | cover(fq_count > 40 && o_qvalid && o_qlast);
923 | end
924 |
925 | // }}}
926 | ////////////////////////////////////////////////////////////////////////
927 | //
928 | // "Careless" assumptions
929 | // {{{
930 | // always @(*) assume(i_qready);
931 | // always @(*) assume(enc_valid);
932 |
933 | // }}}
934 |
935 | // }}}
936 | `endif
937 | // }}}
938 | endmodule
939 |
--------------------------------------------------------------------------------
/rtl/qoi_recorder.v:
--------------------------------------------------------------------------------
1 | ////////////////////////////////////////////////////////////////////////////////
2 | //
3 | // Filename: ./rtl/qoi_recorder.v
4 | // {{{
5 | // Project: Quite OK image compression (QOI) Verilog implementation
6 | //
7 | // Purpose: To write one (or more) video images to memory. We'll use the
8 | // QOI compression scheme to get the bandwidth down. Once
9 | // written, the total capture size may be queried and/or reset for another
10 | // capture. Design generates no backpressure when not in use--allowing
11 | // raw video data to stream through.
12 | //
13 | // Registers:
14 | // 0: Status/Control
15 | // Busy
16 | // Number of frames desired / number of frames remaining
17 | // 4: Address (MSB when not LITTLE ENDIAN)
18 | // 8: Address (LSB when not LITTLE ENDIAN)
19 | // C: Data length allowed
20 | //
21 | // Creator: Dan Gisselquist, Ph.D.
22 | // Gisselquist Technology, LLC
23 | //
24 | ////////////////////////////////////////////////////////////////////////////////
25 | // }}}
26 | // Copyright (C) 2024, Gisselquist Technology, LLC
27 | // {{{
28 | // This program is free software (firmware): you can redistribute it and/or
29 | // modify it under the terms of the GNU General Public License as published
30 | // by the Free Software Foundation, either version 3 of the License, or (at
31 | // your option) any later version.
32 | //
33 | // This program is distributed in the hope that it will be useful, but WITHOUT
34 | // ANY WARRANTY; without even the implied warranty of MERCHANTIBILITY or
35 | // FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
36 | // for more details.
37 | //
38 | // You should have received a copy of the GNU General Public License along
39 | // with this program. (It's in the $(ROOT)/doc directory. Run make with no
40 | // target there if the PDF file isn't present.) If not, see
41 | // for a copy.
42 | // }}}
43 | // License: GPL, v3, as defined and found on www.gnu.org,
44 | // {{{
45 | // http://www.gnu.org/licenses/gpl.html
46 | //
47 | ////////////////////////////////////////////////////////////////////////////////
48 | //
49 | `default_nettype none
50 | // }}}
51 | module qoi_recorder #(
52 | // {{{
53 | parameter [0:0] OPT_COMPRESS = 1'b1,
54 | parameter [0:0] OPT_TUSER_IS_SOF = 1'b0,
55 | parameter ADDRESS_WIDTH = 32,
56 | parameter DW = 64,
57 | parameter AW = ADDRESS_WIDTH-$clog2(DW/8),
58 | parameter LGFIFO = 8
59 | // }}}
60 | ) (
61 | // {{{
62 | input wire i_clk, i_reset,
63 | input wire i_pix_clk,
64 | // Control inputs
65 | // {{{
66 | input wire i_wb_cyc, i_wb_stb, i_wb_we,
67 | input wire [1:0] i_wb_addr,
68 | input wire [31:0] i_wb_data,
69 | input wire [3:0] i_wb_sel,
70 | output wire o_wb_stall,
71 | output reg o_wb_ack,
72 | output reg [31:0] o_wb_data,
73 | // }}}
74 | // Video input interface
75 | // {{{
76 | input wire s_vid_valid,
77 | output wire s_vid_ready,
78 | input wire [23:0] s_vid_data,
79 | input wire s_vid_user, s_vid_last,
80 | // }}}
81 | // Outgoing WB/DMA interface
82 | // {{{
83 | output wire o_dma_cyc, o_dma_stb, o_dma_we,
84 | output wire [AW-1:0] o_dma_addr,
85 | output wire [DW-1:0] o_dma_data,
86 | output wire [DW/8-1:0] o_dma_sel,
87 | input wire i_dma_stall,
88 | input wire i_dma_ack,
89 | input wire [DW-1:0] i_dma_data,
90 | input wire i_dma_err
91 | // }}}
92 | // }}}
93 | );
94 |
95 | // Local declarations
96 | // {{{
97 | localparam ADDR_CTRL= 0,
98 | ADDR_MSW = 1,
99 | ADDR_LSW = 2;
100 |
101 | wire soft_dma_reset;
102 |
103 | wire sel_valid, sel_ready, sel_last;
104 | wire [DW-1:0] sel_data;
105 | wire [$clog2(DW/8)-1:0] sel_bytes;
106 |
107 | wire pix_valid, pix_ready, pix_last;
108 | wire [DW-1:0] pix_data;
109 | wire [$clog2(DW/8):0] pix_bytes;
110 |
111 | wire pxm_valid, pxm_ready, pxm_last;
112 | wire [DW-1:0] pxm_data;
113 | wire [$clog2(DW/8):0] pxm_bytes;
114 |
115 | wire fifo_valid, fifo_ready, fifo_last;
116 | wire [DW-1:0] fifo_data;
117 | wire [$clog2(DW/8):0] fifo_bytes;
118 | reg fifo_flush;
119 |
120 | wire afifo_full, afifo_empty;
121 | wire fifo_full, fifo_empty, fifo_read;
122 |
123 | reg [63:0] wide_dma_address;
124 | reg [15:0] nframes;
125 | reg [AW+$clog2(DW/8)-1:0] dma_address;
126 | wire [AW+$clog2(DW/8)-1:0] base_addr;
127 |
128 | reg dma_request, vid_sync, dma_active;
129 | wire dma_busy, dma_err;
130 |
131 | reg pix_reset, pix_reset_pipe;
132 |
133 | always @(posedge i_pix_clk)
134 | if (i_reset)
135 | { pix_reset, pix_reset_pipe } <= -1;
136 | else
137 | { pix_reset, pix_reset_pipe } <= { pix_reset_pipe, 1'b0 };
138 |
139 | // }}}
140 | ////////////////////////////////////////////////////////////////////////
141 | //
142 | // (Optionally) compress our video data
143 | // {{{
144 | ////////////////////////////////////////////////////////////////////////
145 | //
146 | //
147 |
148 | generate if (OPT_COMPRESS)
149 | begin : GEN_QOI_COMPRESSION
150 | wire [DW-1:0] lcl_data;
151 | wire [$clog2(DW/8)-1:0] lcl_bytes;
152 |
153 | qoi_encoder #(
154 | .OPT_TUSER_IS_SOF(OPT_TUSER_IS_SOF),
155 | .DW(DW)
156 | ) u_compress_video (
157 | // {{{
158 | .i_clk(i_pix_clk),
159 | .i_reset(pix_reset),
160 | //
161 | .s_valid(s_vid_valid),
162 | .s_ready(s_vid_ready),
163 | .s_data(s_vid_data),
164 | .s_last(s_vid_last),
165 | .s_user(s_vid_user),
166 | //
167 | .o_qvalid(sel_valid),
168 | .i_qready(sel_ready),
169 | .o_qdata(lcl_data),
170 | .o_qbytes(lcl_bytes),
171 | .o_qlast(sel_last)
172 | // }}}
173 | );
174 |
175 | assign sel_data = lcl_data;
176 | assign sel_bytes = lcl_bytes;
177 |
178 | end else begin : NO_COMPRESSION
179 | wire s_vid_hlast, s_vid_vlast;
180 |
181 | assign s_vid_hlast = s_vid_user;
182 | assign s_vid_vlast = s_vid_last;
183 |
184 | assign sel_valid = s_vid_valid;
185 | assign s_vid_ready = sel_ready;
186 | assign sel_data = { s_vid_data, {(DW-24){1'b0}} };
187 | assign sel_bytes = 3;
188 | assign sel_last = s_vid_hlast && s_vid_vlast;
189 |
190 | end endgenerate
191 | // }}}
192 | ////////////////////////////////////////////////////////////////////////
193 | //
194 | // Reshape pixels to the full memory width
195 | // {{{
196 | ////////////////////////////////////////////////////////////////////////
197 | //
198 | //
199 |
200 | zipdma_rxgears #(
201 | .BUS_WIDTH(DW),
202 | .OPT_LITTLE_ENDIAN(1'b0)
203 | ) u_rxgears (
204 | .i_clk(i_pix_clk), .i_reset(pix_reset),
205 | .i_soft_reset(soft_dma_reset),
206 | .S_VALID(sel_valid),
207 | .S_READY(sel_ready),
208 | .S_DATA( sel_data),
209 | .S_BYTES({ (sel_bytes == 0 ? 1'b1:1'b0), sel_bytes }),
210 | .S_LAST( sel_last),
211 | //
212 | .M_VALID(pix_valid),
213 | .M_READY(pix_ready),
214 | .M_DATA( pix_data),
215 | .M_BYTES(pix_bytes),
216 | .M_LAST( pix_last)
217 | );
218 | // }}}
219 | ////////////////////////////////////////////////////////////////////////
220 | //
221 | // Cross to the bus clock domain
222 | // {{{
223 | ////////////////////////////////////////////////////////////////////////
224 | //
225 | // Need to cross here from the pixel clock to the memory clock domain.
226 | //
227 | // No particular FIFO depth is required here, since we're just going
228 | // straight to another FIFO. That second FIFO will have the depth.
229 | // Our purpose is just to make sure we can accomplish maximum throughput
230 | // if desired, and hence a min depth of 8 samples or so.
231 | //
232 |
233 | afifo #(
234 | .LGFIFO(3), .WIDTH(2+$clog2(DW/8)+DW)
235 | ) u_afifo (
236 | .i_wclk(i_pix_clk), .i_wr_reset_n(!pix_reset),
237 | .i_wr(pix_valid),
238 | .i_wr_data({ pix_last, pix_bytes, pix_data }),
239 | .o_wr_full(afifo_full),
240 | .i_rclk(i_clk), .i_rd_reset_n(!i_reset),
241 | .i_rd(pxm_ready),
242 | .o_rd_data({ pxm_last, pxm_bytes, pxm_data }),
243 | .o_rd_empty(afifo_empty)
244 | );
245 |
246 | assign pxm_valid = !afifo_empty;
247 | assign pix_ready = !afifo_full;
248 |
249 | // }}}
250 | ////////////////////////////////////////////////////////////////////////
251 | //
252 | // Run everything into a FIFO
253 | // {{{
254 | ////////////////////////////////////////////////////////////////////////
255 | //
256 | //
257 | wire [LGFIFO:0] fifo_fill;
258 |
259 | sfifo #(
260 | .BW(DW+$clog2(DW/8)+2), .LGFLEN(LGFIFO)
261 | ) u_fifo (
262 | .i_clk(i_clk), .i_reset(i_reset),
263 | //
264 | .i_wr(pxm_valid),
265 | .i_data({ pxm_last, pxm_bytes, pxm_data }),
266 | .o_full(fifo_full),
267 | .o_fill(fifo_fill),
268 | //
269 | .i_rd(fifo_read),
270 | .o_data({ fifo_last, fifo_bytes, fifo_data }),
271 | .o_empty(fifo_empty)
272 | );
273 |
274 | assign pxm_ready = !fifo_full;
275 | assign fifo_valid = !fifo_empty;
276 | assign fifo_read = (fifo_ready && fifo_flush) || !dma_active;
277 | // }}}
278 | ////////////////////////////////////////////////////////////////////////
279 | //
280 | // Synchronize
281 | // {{{
282 | ////////////////////////////////////////////////////////////////////////
283 | //
284 | //
285 |
286 | always @(posedge i_clk)
287 | if (i_reset)
288 | vid_sync <= 1'b1;
289 | else if (fifo_read && !fifo_empty && fifo_last)
290 | vid_sync <= 1'b1;
291 | else if (fifo_read && !fifo_empty && !dma_active)
292 | vid_sync <= 1'b0;
293 |
294 | always @(posedge i_clk)
295 | if (i_reset)
296 | dma_active <= 1'b0;
297 | else if (!dma_active)
298 | begin
299 | if ((dma_request || dma_busy) && vid_sync)
300 | dma_active <= !fifo_read || fifo_empty;
301 | end else if (fifo_read && !fifo_empty && fifo_last && !dma_request)
302 | dma_active <= 1'b0;
303 |
304 | always @(posedge i_clk)
305 | if (i_reset)
306 | fifo_flush <= 1'b0;
307 | else if (fifo_read && fifo_empty)
308 | fifo_flush <= 1'b0;
309 | else if (pxm_valid && pxm_last)
310 | fifo_flush <= 1'b1;
311 | else if (fifo_fill[LGFIFO:LGFIFO-1] != 2'b00)
312 | fifo_flush <= 1'b1;
313 |
314 | // }}}
315 | ////////////////////////////////////////////////////////////////////////
316 | //
317 | // Write the final results to memory
318 | // {{{
319 | ////////////////////////////////////////////////////////////////////////
320 | //
321 | //
322 |
323 | zipdma_s2mm #(
324 | .ADDRESS_WIDTH(ADDRESS_WIDTH), .BUS_WIDTH(DW)
325 | ) u_dma (
326 | .i_clk(i_clk), .i_reset(i_reset),
327 | //
328 | .i_request(dma_request), .o_busy(dma_busy), .o_err(dma_err),
329 | // Always increment. Size is always the full bus size.
330 | .i_inc(1'b1), .i_size(2'b00), .i_addr(base_addr),
331 | //
332 | .S_VALID(fifo_valid && dma_active && fifo_flush),
333 | .S_READY(fifo_ready),
334 | .S_DATA(fifo_data), .S_BYTES(fifo_bytes), .S_LAST(fifo_last),
335 | //
336 | .o_wr_cyc(o_dma_cyc), .o_wr_stb(o_dma_stb), .o_wr_we(o_dma_we),
337 | .o_wr_addr(o_dma_addr), .o_wr_data(o_dma_data),
338 | .o_wr_sel(o_dma_sel),
339 | .i_wr_stall(i_dma_stall), .i_wr_ack(i_dma_ack),
340 | .i_wr_data(i_dma_data),
341 | .i_wr_err(i_dma_err)
342 | );
343 | // }}}
344 | ////////////////////////////////////////////////////////////////////////
345 | //
346 | // Control bus handling
347 | // {{{
348 | assign o_wb_stall = 1'b0;
349 | assign soft_dma_reset = 1'b0;
350 |
351 | always @(posedge i_clk)
352 | if (i_reset)
353 | begin
354 | nframes <= 0;
355 | dma_request <= 0;
356 | end else if (dma_err || (o_dma_cyc && i_dma_err))
357 | begin
358 | dma_request <= 0;
359 | nframes <= 0;
360 | end else begin
361 | if (dma_active && fifo_read && !fifo_empty && fifo_last)
362 | begin
363 | if (nframes > 0)
364 | nframes <= nframes - 1;
365 | if (nframes <= 1)
366 | dma_request <= 0;
367 | end
368 |
369 | if (i_wb_stb && !o_wb_stall && !dma_request && i_wb_addr == 0
370 | && i_wb_sel[1:0] == 2'b11
371 | && i_wb_data[15:0] != 0 && dma_address != 0)
372 | begin
373 | nframes <= i_wb_data[15:0];
374 | dma_request <= 1'b1;
375 | end
376 | end
377 |
378 | always @(*)
379 | begin
380 | wide_dma_address = { {(64-AW-$clog2(DW/8)){1'b0}}, dma_address };
381 | if (i_wb_stb && !o_wb_stall && i_wb_we && i_wb_addr == ADDR_LSW)
382 | begin
383 | if (i_wb_sel[0])
384 | wide_dma_address[ 7: 0] = i_wb_data[ 7: 0];
385 | if (i_wb_sel[1])
386 | wide_dma_address[15: 8] = i_wb_data[15: 8];
387 | if (i_wb_sel[2])
388 | wide_dma_address[23:16] = i_wb_data[23:16];
389 | if (i_wb_sel[3])
390 | wide_dma_address[31:24] = i_wb_data[31:24];
391 | end
392 |
393 | if (i_wb_stb && !o_wb_stall && i_wb_we && i_wb_addr == ADDR_MSW)
394 | begin
395 | if (i_wb_sel[0])
396 | wide_dma_address[39:32] = i_wb_data[ 7: 0];
397 | if (i_wb_sel[1])
398 | wide_dma_address[47:40] = i_wb_data[15: 8];
399 | if (i_wb_sel[2])
400 | wide_dma_address[55:48] = i_wb_data[23:16];
401 | if (i_wb_sel[3])
402 | wide_dma_address[63:56] = i_wb_data[31:24];
403 | end
404 |
405 | wide_dma_address[63:AW+$clog2(DW/8)] = 0;
406 | end
407 |
408 | always @(posedge i_clk)
409 | if (i_reset)
410 | begin
411 | dma_address <= 0;
412 | end else begin
413 | if (dma_active && fifo_read && !fifo_empty)
414 | begin
415 | // Verilator lint_off WIDTH
416 | dma_address <= dma_address + fifo_bytes;
417 | // Verilator lint_on WIDTH
418 | end
419 |
420 | if (i_wb_stb && !o_wb_stall && !dma_busy && !dma_request
421 | && (i_wb_addr == 1 || i_wb_addr == 2))
422 | begin
423 | dma_address[AW+$clog2(DW/8)-1:0] <= wide_dma_address[AW+$clog2(DW/8)-1:0];
424 | end
425 | end
426 |
427 | assign base_addr = dma_address;
428 |
429 | initial o_wb_data = 0;
430 | always @(posedge i_clk)
431 | if (i_wb_stb)
432 | begin
433 | case(i_wb_addr)
434 | ADDR_CTRL: o_wb_data
435 | <= { dma_request, dma_busy, dma_err, dma_active,
436 | vid_sync, 11'h0, nframes };
437 | ADDR_LSW: o_wb_data <= wide_dma_address[31:0];
438 | ADDR_MSW: o_wb_data <= wide_dma_address[63:32];
439 | default: o_wb_data <= 0;
440 | endcase
441 | end
442 |
443 | always @(posedge i_clk)
444 | if (i_reset)
445 | o_wb_ack <= 1'b0;
446 | else
447 | o_wb_ack <= i_wb_stb && !o_wb_stall;
448 |
449 | // }}}
450 |
451 | // Keep Verilator happy
452 | // {{{
453 | // Verilator coverage_off
454 | // Verilator lint_off UNUSED
455 | wire unused = &{ 1'b0, i_wb_cyc, fifo_fill };
456 | // Verilator lint_on UNUSED
457 | // Verilator coverage_on
458 | // }}}
459 | endmodule
460 |
--------------------------------------------------------------------------------
/rtl/qoi_skid.v:
--------------------------------------------------------------------------------
1 | ////////////////////////////////////////////////////////////////////////////////
2 | //
3 | // Filename: ./rtl/qoi_skid.v
4 | // {{{
5 | // Project: Quite OK image compression (QOI) Verilog implementation
6 | //
7 | // Purpose: A basic SKID buffer.
8 | // {{{
9 | // Skid buffers are required for high throughput AXI code, since the AXI
10 | // specification requires that all outputs be registered. This means
11 | // that, if there are any stall conditions calculated, it will take a clock
12 | // cycle before the stall can be propagated up stream. This means that
13 | // the data will need to be buffered for a cycle until the stall signal
14 | // can make it to the output.
15 | //
16 | // Handling that buffer is the purpose of this core.
17 | //
18 | // On one end of this core, you have the i_valid and i_data inputs to
19 | // connect to your bus interface. There's also a registered o_ready
20 | // signal to signal stalls for the bus interface.
21 | //
22 | // The other end of the core has the same basic interface, but it isn't
23 | // registered. This allows you to interact with the bus interfaces
24 | // as though they were combinatorial logic, by interacting with this half
25 | // of the core.
26 | //
27 | // If at any time the incoming !stall signal, i_ready, signals a stall,
28 | // the incoming data is placed into a buffer. Internally, that buffer
29 | // is held in r_data with the r_valid flag used to indicate that valid
30 | // data is within it.
31 | // }}}
32 | // Parameters:
33 | // {{{
34 | // DW or data width
35 | // In order to make this core generic, the width of the data in the
36 | // skid buffer is parameterized
37 | //
38 | // OPT_LOWPOWER
39 | // Forces both o_data and r_data to zero if the respective *VALID
40 | // signal is also low. While this costs extra logic, it can also
41 | // be used to guarantee that any unused values aren't toggling and
42 | // therefore unnecessarily using power.
43 | //
44 | // This excess toggling can be particularly problematic if the
45 | // bus signals have a high fanout rate, or a long signal path
46 | // across an FPGA.
47 | //
48 | // OPT_OUTREG
49 | // Causes the outputs to be registered
50 | //
51 | // OPT_PASSTHROUGH
52 | // Turns the skid buffer into a passthrough. Used for formal
53 | // verification only.
54 | // }}}
55 | // Creator: Dan Gisselquist, Ph.D.
56 | // Gisselquist Technology, LLC
57 | //
58 | ////////////////////////////////////////////////////////////////////////////////
59 | // }}}
60 | // Copyright (C) 2018-2024, Gisselquist Technology, LLC
61 | // {{{
62 | // This program is free software (firmware): you can redistribute it and/or
63 | // modify it under the terms of the GNU General Public License as published
64 | // by the Free Software Foundation, either version 3 of the License, or (at
65 | // your option) any later version.
66 | //
67 | // This program is distributed in the hope that it will be useful, but WITHOUT
68 | // ANY WARRANTY; without even the implied warranty of MERCHANTIBILITY or
69 | // FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
70 | // for more details.
71 | //
72 | // You should have received a copy of the GNU General Public License along
73 | // with this program. (It's in the $(ROOT)/doc directory. Run make with no
74 | // target there if the PDF file isn't present.) If not, see
75 | // for a copy.
76 | // }}}
77 | // License: GPL, v3, as defined and found on www.gnu.org,
78 | // {{{
79 | // http://www.gnu.org/licenses/gpl.html
80 | //
81 | ////////////////////////////////////////////////////////////////////////////////
82 | //
83 | //
84 | `default_nettype none
85 | `timescale 1ns/1ps
86 | // }}}
87 | module qoi_skid #(
88 | // {{{
89 | parameter [0:0] OPT_LOWPOWER = 0,
90 | parameter [0:0] OPT_OUTREG = 1,
91 | //
92 | parameter [0:0] OPT_PASSTHROUGH = 0,
93 | parameter DW = 8,
94 | parameter [0:0] OPT_INITIAL = 1'b1
95 | // }}}
96 | ) (
97 | // {{{
98 | input wire i_clk, i_reset,
99 | input wire i_valid,
100 | output wire o_ready,
101 | input wire [DW-1:0] i_data,
102 | output wire o_valid,
103 | input wire i_ready,
104 | output reg [DW-1:0] o_data
105 | // }}}
106 | );
107 |
108 | wire [DW-1:0] w_data;
109 |
110 | generate if (OPT_PASSTHROUGH)
111 | begin : PASSTHROUGH
112 | // {{{
113 | assign { o_valid, o_ready } = { i_valid, i_ready };
114 |
115 | always @(*)
116 | if (!i_valid && OPT_LOWPOWER)
117 | o_data = 0;
118 | else
119 | o_data = i_data;
120 |
121 | assign w_data = 0;
122 |
123 | // Keep Verilator happy
124 | // Verilator lint_off UNUSED
125 | // {{{
126 | wire unused_passthrough;
127 | assign unused_passthrough = &{ 1'b0, i_clk, i_reset };
128 | // }}}
129 | // Verilator lint_on UNUSED
130 | // }}}
131 | end else begin : LOGIC
132 | // We'll start with skid buffer itself
133 | // {{{
134 | reg r_valid;
135 | reg [DW-1:0] r_data;
136 |
137 | // r_valid
138 | // {{{
139 | initial if (OPT_INITIAL) r_valid = 0;
140 | always @(posedge i_clk)
141 | if (i_reset)
142 | r_valid <= 0;
143 | else if ((i_valid && o_ready) && (o_valid && !i_ready))
144 | // We have incoming data, but the output is stalled
145 | r_valid <= 1;
146 | else if (i_ready)
147 | r_valid <= 0;
148 | // }}}
149 |
150 | // r_data
151 | // {{{
152 | initial if (OPT_INITIAL) r_data = 0;
153 | always @(posedge i_clk)
154 | if (OPT_LOWPOWER && i_reset)
155 | r_data <= 0;
156 | else if (OPT_LOWPOWER && (!o_valid || i_ready))
157 | r_data <= 0;
158 | else if ((!OPT_LOWPOWER || !OPT_OUTREG || i_valid) && o_ready)
159 | r_data <= i_data;
160 |
161 | assign w_data = r_data;
162 | // }}}
163 |
164 | // o_ready
165 | // {{{
166 | assign o_ready = !r_valid;
167 | // }}}
168 |
169 | //
170 | // And then move on to the output port
171 | //
172 | if (!OPT_OUTREG)
173 | begin : NET_OUTPUT
174 | // Outputs are combinatorially determined from inputs
175 | // {{{
176 | // o_valid
177 | // {{{
178 | assign o_valid = !i_reset && (i_valid || r_valid);
179 | // }}}
180 |
181 | // o_data
182 | // {{{
183 | always @(*)
184 | if (r_valid)
185 | o_data = r_data;
186 | else if (!OPT_LOWPOWER || i_valid)
187 | o_data = i_data;
188 | else
189 | o_data = 0;
190 | // }}}
191 | // }}}
192 | end else begin : REG_OUTPUT
193 | // Register our outputs
194 | // {{{
195 | // o_valid
196 | // {{{
197 | reg ro_valid;
198 |
199 | initial if (OPT_INITIAL) ro_valid = 0;
200 | always @(posedge i_clk)
201 | if (i_reset)
202 | ro_valid <= 0;
203 | else if (!o_valid || i_ready)
204 | ro_valid <= (i_valid || r_valid);
205 |
206 | assign o_valid = ro_valid;
207 | // }}}
208 |
209 | // o_data
210 | // {{{
211 | initial if (OPT_INITIAL) o_data = 0;
212 | always @(posedge i_clk)
213 | if (OPT_LOWPOWER && i_reset)
214 | o_data <= 0;
215 | else if (!o_valid || i_ready)
216 | begin
217 |
218 | if (r_valid)
219 | o_data <= r_data;
220 | else if (!OPT_LOWPOWER || i_valid)
221 | o_data <= i_data;
222 | else
223 | o_data <= 0;
224 | end
225 | // }}}
226 |
227 | // }}}
228 | end
229 | // }}}
230 | end endgenerate
231 |
232 | // Keep Verilator happy
233 | // {{{
234 | // Verilator lint_off UNUSED
235 | wire unused;
236 | assign unused = &{ 1'b0, w_data };
237 | // Verilator lint_on UNUSED
238 | // }}}
239 | ////////////////////////////////////////////////////////////////////////////////
240 | ////////////////////////////////////////////////////////////////////////////////
241 | ////////////////////////////////////////////////////////////////////////////////
242 | //
243 | // Formal properties
244 | // {{{
245 | ////////////////////////////////////////////////////////////////////////////////
246 | ////////////////////////////////////////////////////////////////////////////////
247 | ////////////////////////////////////////////////////////////////////////////////
248 | `ifdef FORMAL
249 | `ifdef SKIDBUFFER
250 | `define ASSUME assume
251 | `else
252 | `define ASSUME assert
253 | `endif
254 |
255 | reg f_past_valid;
256 |
257 | initial f_past_valid = 0;
258 | always @(posedge i_clk)
259 | f_past_valid <= 1;
260 |
261 | always @(*)
262 | if (!f_past_valid)
263 | assume(i_reset);
264 |
265 | ////////////////////////////////////////////////////////////////////////
266 | //
267 | // Incoming stream properties / assumptions
268 | // {{{
269 | ////////////////////////////////////////////////////////////////////////
270 | //
271 | always @(posedge i_clk)
272 | if (!f_past_valid)
273 | begin
274 | `ASSUME(!i_valid || !OPT_INITIAL);
275 | end else if ($past(i_valid && !o_ready && !i_reset) && !i_reset)
276 | `ASSUME(i_valid && $stable(i_data));
277 |
278 | `ifdef VERIFIC
279 | `define FORMAL_VERIFIC
280 | // Reset properties
281 | property RESET_CLEARS_IVALID;
282 | @(posedge i_clk) i_reset |=> !i_valid;
283 | endproperty
284 |
285 | property IDATA_HELD_WHEN_NOT_READY;
286 | @(posedge i_clk) disable iff (i_reset)
287 | i_valid && !o_ready |=> i_valid && $stable(i_data);
288 | endproperty
289 |
290 | `ifdef SKIDBUFFER
291 | assume property (IDATA_HELD_WHEN_NOT_READY);
292 | `else
293 | assert property (IDATA_HELD_WHEN_NOT_READY);
294 | `endif
295 | `endif
296 | // }}}
297 | ////////////////////////////////////////////////////////////////////////
298 | //
299 | // Outgoing stream properties / assumptions
300 | // {{{
301 | ////////////////////////////////////////////////////////////////////////
302 | //
303 |
304 | generate if (!OPT_PASSTHROUGH)
305 | begin
306 |
307 | always @(posedge i_clk)
308 | if (!f_past_valid) // || $past(i_reset))
309 | begin
310 | // Following any reset, valid must be deasserted
311 | assert(!o_valid || !OPT_INITIAL);
312 | end else if ($past(o_valid && !i_ready && !i_reset) && !i_reset)
313 | // Following any stall, valid must remain high and
314 | // data must be preserved
315 | assert(o_valid && $stable(o_data));
316 |
317 | end endgenerate
318 | // }}}
319 | ////////////////////////////////////////////////////////////////////////
320 | //
321 | // Other properties
322 | // {{{
323 | ////////////////////////////////////////////////////////////////////////
324 | //
325 | //
326 | generate if (!OPT_PASSTHROUGH)
327 | begin
328 | // Rule #1:
329 | // If registered, then following any reset we should be
330 | // ready for a new request
331 | // {{{
332 | always @(posedge i_clk)
333 | if (f_past_valid && $past(OPT_OUTREG && i_reset))
334 | assert(o_ready);
335 | // }}}
336 |
337 | // Rule #2:
338 | // All incoming data must either go directly to the
339 | // output port, or into the skid buffer
340 | // {{{
341 | `ifndef VERIFIC
342 | always @(posedge i_clk)
343 | if (f_past_valid && !$past(i_reset) && $past(i_valid && o_ready
344 | && (!OPT_OUTREG || o_valid) && !i_ready))
345 | assert(!o_ready && w_data == $past(i_data));
346 | `else
347 | assert property (@(posedge i_clk)
348 | disable iff (i_reset)
349 | (i_valid && o_ready
350 | && (!OPT_OUTREG || o_valid) && !i_ready)
351 | |=> (!o_ready && w_data == $past(i_data)));
352 | `endif
353 | // }}}
354 |
355 | // Rule #3:
356 | // After the last transaction, o_valid should become idle
357 | // {{{
358 | if (!OPT_OUTREG)
359 | begin
360 | // {{{
361 | always @(posedge i_clk)
362 | if (f_past_valid && !$past(i_reset) && !i_reset
363 | && $past(i_ready))
364 | begin
365 | assert(o_valid == i_valid);
366 | assert(!i_valid || (o_data == i_data));
367 | end
368 | // }}}
369 | end else begin
370 | // {{{
371 | always @(posedge i_clk)
372 | if (f_past_valid && !$past(i_reset))
373 | begin
374 | if ($past(i_valid && o_ready))
375 | assert(o_valid);
376 |
377 | if ($past(!i_valid && o_ready && i_ready))
378 | assert(!o_valid);
379 | end
380 | // }}}
381 | end
382 | // }}}
383 |
384 | // Rule #4
385 | // Same thing, but this time for o_ready
386 | // {{{
387 | always @(posedge i_clk)
388 | if (f_past_valid && $past(!o_ready && i_ready))
389 | assert(o_ready);
390 | // }}}
391 |
392 | // If OPT_LOWPOWER is set, o_data and w_data both need to be
393 | // zero any time !o_valid or !r_valid respectively
394 | // {{{
395 | if (OPT_LOWPOWER)
396 | begin
397 | always @(*)
398 | if ((OPT_OUTREG || !i_reset) && !o_valid)
399 | assert(o_data == 0);
400 |
401 | always @(*)
402 | if (o_ready)
403 | assert(w_data == 0);
404 |
405 | end
406 | // }}}
407 | end endgenerate
408 | // }}}
409 | ////////////////////////////////////////////////////////////////////////
410 | //
411 | // Cover checks
412 | // {{{
413 | ////////////////////////////////////////////////////////////////////////
414 | //
415 | //
416 | `ifdef SKIDBUFFER
417 | generate if (!OPT_PASSTHROUGH)
418 | begin
419 | reg f_changed_data;
420 |
421 | initial f_changed_data = 0;
422 | always @(posedge i_clk)
423 | if (i_reset)
424 | f_changed_data <= 1;
425 | else if (i_valid && $past(!i_valid || o_ready))
426 | begin
427 | if (i_data != $past(i_data + 1))
428 | f_changed_data <= 0;
429 | end else if (!i_valid && i_data != 0)
430 | f_changed_data <= 0;
431 |
432 |
433 | `ifndef VERIFIC
434 | reg [3:0] cvr_steps, cvr_hold;
435 |
436 | always @(posedge i_clk)
437 | if (i_reset)
438 | begin
439 | cvr_steps <= 0;
440 | cvr_hold <= 0;
441 | end else begin
442 | cvr_steps <= cvr_steps + 1;
443 | cvr_hold <= cvr_hold + 1;
444 | case(cvr_steps)
445 | 0: if (o_valid || i_valid)
446 | cvr_steps <= 0;
447 | 1: if (!i_valid || !i_ready)
448 | cvr_steps <= 0;
449 | 2: if (!i_valid || !i_ready)
450 | cvr_steps <= 0;
451 | 3: if (!i_valid || !i_ready)
452 | cvr_steps <= 0;
453 | 4: if (!i_valid || i_ready)
454 | cvr_steps <= 0;
455 | 5: if (!i_valid || !i_ready)
456 | cvr_steps <= 0;
457 | 6: if (!i_valid || !i_ready)
458 | cvr_steps <= 0;
459 | 7: if (!i_valid || i_ready)
460 | cvr_steps <= 0;
461 | 8: if (!i_valid || i_ready)
462 | cvr_steps <= 0;
463 | 9: if (!i_valid || !i_ready)
464 | cvr_steps <= 0;
465 | 10: if (!i_valid || !i_ready)
466 | cvr_steps <= 0;
467 | 11: if (!i_valid || !i_ready)
468 | cvr_steps <= 0;
469 | 12: begin
470 | cvr_steps <= cvr_steps;
471 | cover(!o_valid && !i_valid && f_changed_data);
472 | if (!o_valid || !i_ready)
473 | cvr_steps <= 0;
474 | else
475 | cvr_hold <= cvr_hold + 1;
476 | end
477 | default: assert(0);
478 | endcase
479 | end
480 |
481 | `else
482 | // Cover test
483 | cover property (@(posedge i_clk)
484 | disable iff (i_reset)
485 | (!o_valid && !i_valid)
486 | ##1 i_valid && i_ready [*3]
487 | ##1 i_valid && !i_ready
488 | ##1 i_valid && i_ready [*2]
489 | ##1 i_valid && !i_ready [*2]
490 | ##1 i_valid && i_ready [*3]
491 | // Wait for the design to clear
492 | ##1 o_valid && i_ready [*0:5]
493 | ##1 (!o_valid && !i_valid && f_changed_data));
494 | `endif
495 | end endgenerate
496 | `endif // SKIDBUFFER
497 | // }}}
498 | `endif
499 | // }}}
500 | endmodule
501 |
--------------------------------------------------------------------------------