├── INSTALL.md ├── LICENSE ├── README.md ├── cpu ├── Makefile ├── README.md ├── alu_data.vhd ├── alu_flags.vhd ├── axi_pause.vhd ├── cpu.png ├── cpu.vhd ├── cpu_constants.vhd ├── decode.vhd ├── dp_ram.vhd ├── execute.vhd ├── microcode.vhd ├── prog.asm ├── prog_simple.asm ├── registers.vhd ├── system.vhd ├── system.xdc ├── tb_cpu.gtkw └── tb_cpu.vhd ├── fetch ├── Makefile ├── README.md ├── fetch.gtkw ├── fetch.psl ├── fetch.sby ├── fetch.vhd └── waveform.png ├── fetch2 ├── Makefile ├── README.md ├── fetch.gtkw ├── fetch.psl ├── fetch.sby ├── fetch.vhd └── waveform.png ├── fgc ├── Makefile ├── README.md ├── fgc.gtkw ├── fgc.psl ├── fgc.sby ├── fgc.vhd └── waveform.png ├── memory ├── Makefile ├── README.md ├── memory.gtkw ├── memory.psl ├── memory.sby ├── memory.vhd └── waveform.png ├── one_stage_buffer ├── Makefile ├── README.md ├── cover_statement.png ├── one_stage_buffer.gtkw ├── one_stage_buffer.psl ├── one_stage_buffer.sby └── one_stage_buffer.vhd ├── one_stage_fifo ├── Makefile ├── README.md ├── cover_statement.png ├── one_stage_fifo.gtkw ├── one_stage_fifo.psl ├── one_stage_fifo.sby └── one_stage_fifo.vhd ├── pipe_concat ├── Makefile ├── README.md ├── pipe_concat.gtkw ├── pipe_concat.psl ├── pipe_concat.sby └── pipe_concat.vhd ├── rubik ├── Makefile ├── README.md ├── rubik.gtkw ├── rubik.psl ├── rubik.sby ├── rubik.vhd ├── rubik_sim.gtkw ├── rubik_tb.vhd └── waveform.png ├── rubik2 ├── Makefile ├── README.md ├── rubik.gtkw ├── rubik.psl ├── rubik.sby ├── rubik.vhd ├── rubik_sim.gtkw ├── rubik_tb.vhd ├── swapped.png ├── twisted.png └── waveform.png ├── two_stage_buffer ├── Makefile ├── README.md ├── two_stage_buffer.gtkw ├── two_stage_buffer.psl ├── two_stage_buffer.sby ├── two_stage_buffer.vhd └── waveform.png ├── two_stage_fifo ├── Makefile ├── README.md ├── two_stage_fifo.gtkw ├── two_stage_fifo.psl ├── two_stage_fifo.sby ├── two_stage_fifo.vhd └── waveform.png ├── wb_mem ├── Makefile ├── README.md ├── wb_mem.gtkw ├── wb_mem.psl ├── wb_mem.sby └── wb_mem.vhd └── wb_tdp_mem ├── Makefile ├── README.md ├── tdp_ram.vhd ├── wb_tdp_mem.gtkw ├── wb_tdp_mem.psl ├── wb_tdp_mem.sby └── wb_tdp_mem.vhd /INSTALL.md: -------------------------------------------------------------------------------- 1 | # Installing tools for formal verification 2 | 3 | Getting formal verification to work in VHDL requires quite a lot of manual 4 | setup and install; it would seem the tools are not quite mature yet. 5 | Anyway, I managed to get it working in the end. 6 | 7 | In order to use formal verification of VHDL, you need to install a bunch of tools, as explained below: 8 | * Yosys, SymbiYosys, and some SAT solvers 9 | * GNAT, GHDL, and the ghdl-yosys-plugin 10 | 11 | ## Install Yosys, SymbiYosys, and some SAT solvers 12 | This is described in detail in [this link](https://symbiyosys.readthedocs.io/en/latest/install.html) 13 | and is actually pretty straight forward. 14 | 15 | However, I did run into two problems: 16 | 17 | The first problem is I needed to install an additional package: 18 | 19 | ``` 20 | sudo apt install python3-sphinx 21 | ``` 22 | 23 | The second problem is that on my machine (running Linux Mint 19.3 with GCC 9.3) 24 | I ran into the following error when building the Avy project: 25 | 26 | ``` 27 | extavy/avy/src/ItpMinisat.h:127:52: error: cannot convert ‘boost::logic::tribool’ to ‘bool’ in return 28 | 127 | bool isSolved () { return m_Trivial || m_State || !m_State; } 29 | | ~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~ 30 | | | 31 | | boost::logic::tribool 32 | ``` 33 | 34 | I fixed this by applying the following patch: 35 | 36 | ``` 37 | extavy/avy ((0db110e...)) $ git diff 38 | diff --git a/src/ItpGlucose.h b/src/ItpGlucose.h 39 | index 657253d..4ffe55f 100644 40 | --- a/src/ItpGlucose.h 41 | +++ b/src/ItpGlucose.h 42 | @@ -126,7 +126,7 @@ namespace avy 43 | ::Glucose::Solver* get () { return m_pSat; } 44 | 45 | /// true if the context is decided 46 | - bool isSolved () { return m_Trivial || m_State || !m_State; } 47 | + bool isSolved () { return bool{m_Trivial || m_State || !m_State}; } 48 | 49 | int core (int **out) 50 | { 51 | @@ -182,7 +182,7 @@ namespace avy 52 | bool getVarVal(int v) 53 | { 54 | ::Glucose::Var x = v; 55 | - return tobool (m_pSat->modelValue(x)); 56 | + return bool{tobool (m_pSat->modelValue(x))}; 57 | } 58 | }; 59 | 60 | diff --git a/src/ItpMinisat.h b/src/ItpMinisat.h 61 | index d145d7c..7514f31 100644 62 | --- a/src/ItpMinisat.h 63 | +++ b/src/ItpMinisat.h 64 | @@ -124,7 +124,7 @@ namespace avy 65 | ::Minisat::Solver* get () { return m_pSat.get (); } 66 | 67 | /// true if the context is decided 68 | - bool isSolved () { return m_Trivial || m_State || !m_State; } 69 | + bool isSolved () { return bool{m_Trivial || m_State || !m_State}; } 70 | 71 | int core (int **out) 72 | { 73 | ``` 74 | 75 | Everything else worked fine just by following the instructions in the above link. 76 | 77 | ## Install GNAT, GHDL, and the ghdl-yosys-plugin 78 | 79 | The details are described [here](https://github.com/ghdl/ghdl-yosys-plugin) and is again 80 | pretty straight forward. 81 | 82 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Michael Jørgensen 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Formal verification 2 | 3 | Others have gone here before me, and now it is my turn! 4 | 5 | [Formal verification](http://zipcpu.com/formal/formal.html) is a tool for 6 | verifying the correctness of your implementation. Traditional verification 7 | strategies have relied on hand-crafted testbenches to provide stimuli to the 8 | DUT. Formal verification aims to automate that process. In my view these two 9 | approaches (testbench and formal) supplement each other, rather than replace 10 | each other. 11 | 12 | ## Installing the tools 13 | I've written a [separate document](INSTALL.md) with a guide on how to install 14 | all the necessary tools. 15 | 16 | ## Doing formal verification in VHDL 17 | To use formal verification with VHDL, we need to learn [a new language 18 | PSL](http://www.project-veripage.com/psl_tutorial_1.php). The VHDL file is 19 | augmented with verification commands like `assert`, `assume`, and `cover`. 20 | Furthermore, the SymbiYosys tools must be started with some additional command 21 | line parameters. This is demonstrated in the below examples. 22 | 23 | ## Example designs using formal verification 24 | * [One Stage Fifo](one_stage_fifo/). This is a kind of "hello world" of formal verification. 25 | * [One Stage Buffer](one_stage_buffer/). Another simple but useful module. 26 | * [Two Stage Fifo](two_stage_fifo/). Small FIFO useful for timing closure. 27 | * [Two Stage Buffer](two_stage_buffer/). Small FIFO useful for timing closure. 28 | * [Pipe_Concat](pipe_concat/). Concatenate two elastic pipe streams. 29 | * [Wishbone memory](wb_mem/). This is to learn about the wishbone bus protocol. 30 | * [Fetch](fetch/). The first "real" module. This is a simple instruction fetch module for a CPU. 31 | * [Fetch2](fetch2/). A second (more optimized) implementation of the instruction fetch module. 32 | 33 | ## Example puzzles using formal verification 34 | * [Fox, Goat, and Cabbage](fgc). This uses formal verification to solve a well-known puzzle. 35 | * [Rubik's 2x2x2](rubik). This uses formal verification to solve Rubik's 2x2x2 cube. 36 | 37 | ## Other resources 38 | * [This video](https://www.youtube.com/watch?v=H3tsP9tjYdY) gives a nice 39 | introduction to formal verification, including a lot of small and easy 40 | examples. 41 | 42 | * [This video-series](https://www.youtube.com/watch?v=_5R35QFsXM4) gives a more 43 | detailed tutorial for getting started with formal verification. 44 | 45 | * [Robert Baruch](https://www.youtube.com/watch?v=85ZCTuekjGA) has made a video 46 | series on building a 6800 CPU using 47 | [nMigen](https://github.com/nmigen/nmigen) and applying formal verification 48 | in the process. This was the first time I heard about formal verification, and 49 | has been a great inspiration for me. 50 | 51 | * [Charles LaForest](http://fpgacpu.ca/fpga/index.html) has compiled a huge 52 | resource on VHDL design elements. There is no formal verification, but this 53 | website is a good resource, with detailed explanation of each module. 54 | 55 | -------------------------------------------------------------------------------- /cpu/Makefile: -------------------------------------------------------------------------------- 1 | # Available make targets: 2 | # 'make' runs the simulation 3 | # 'make system.bit' runs Vivado synthesis and bitfile generation 4 | # 'make synth' runs Yosys synthesis 5 | 6 | XILINX_DIR = /opt/Xilinx/Vivado/2019.2 7 | 8 | SOURCES += axi_pause.vhd 9 | SOURCES += cpu_constants.vhd 10 | SOURCES += ../one_stage_fifo/one_stage_fifo.vhd 11 | SOURCES += ../two_stage_fifo/two_stage_fifo.vhd 12 | SOURCES += ../one_stage_buffer/one_stage_buffer.vhd 13 | SOURCES += ../two_stage_buffer/two_stage_buffer.vhd 14 | SOURCES += ../pipe_concat/pipe_concat.vhd 15 | SOURCES += ../fetch2/fetch.vhd 16 | SOURCES += ../memory/memory.vhd 17 | SOURCES += alu_data.vhd 18 | SOURCES += alu_flags.vhd 19 | SOURCES += microcode.vhd 20 | SOURCES += decode.vhd 21 | SOURCES += execute.vhd 22 | SOURCES += dp_ram.vhd 23 | SOURCES += registers.vhd 24 | SOURCES += cpu.vhd 25 | SOURCES += ../wb_tdp_mem/tdp_ram.vhd 26 | SOURCES += ../wb_tdp_mem/wb_tdp_mem.vhd 27 | SOURCES += system.vhd 28 | TOP = system 29 | 30 | ASM = prog.asm 31 | ROM = prog.rom 32 | 33 | TB = tb_cpu 34 | TB_SRC += $(TB).vhd 35 | WAVE = $(TB).ghw 36 | SAVE = $(TB).gtkw 37 | 38 | ASSEMBLER = $(HOME)/git/sy2002/QNICE-FPGA/assembler/asm 39 | 40 | show: $(WAVE) 41 | gtkwave $(WAVE) $(SAVE) 42 | 43 | $(WAVE): $(SOURCES) $(TB_SRC) $(ROM) 44 | ghdl -i --std=08 $(SOURCES) $(TB_SRC) 45 | ghdl -m --std=08 -frelaxed $(TB) 46 | ghdl -r --std=08 -frelaxed $(TB) --wave=$(WAVE) --stop-time=3000us 47 | 48 | $(ROM): $(ASM) 49 | $(ASSEMBLER) $(ASM) 50 | 51 | $(TOP).bit: $(TOP).tcl $(SOURCES) $(TOP).xdc $(ROM) 52 | bash -c "source $(XILINX_DIR)/settings64.sh ; vivado -mode tcl -source $<" 53 | 54 | $(TOP).tcl: Makefile 55 | echo "# This is a tcl command script for the Vivado tool chain" > $@ 56 | echo "read_vhdl -vhdl2008 { $(SOURCES) }" >> $@ 57 | echo "read_xdc $(TOP).xdc" >> $@ 58 | echo "synth_design -top $(TOP) -part xc7a100tcsg324-1 -flatten_hierarchy none" >> $@ 59 | echo "write_checkpoint -force post_synth.dcp" >> $@ 60 | echo "opt_design -directive NoBramPowerOpt" >> $@ 61 | echo "place_design" >> $@ 62 | echo "route_design" >> $@ 63 | echo "write_checkpoint -force post_route.dcp" >> $@ 64 | echo "write_bitstream -force $(TOP).bit" >> $@ 65 | echo "exit" >> $@ 66 | 67 | synth: $(SOURCES) $(ROM) 68 | ghdl -a --std=08 -frelaxed $(SOURCES) 69 | yosys -m ghdl -p 'ghdl --std=08 -frelaxed $(TOP); synth_xilinx -top $(TOP) -edif $(TOP).edif' > yosys.log 70 | 71 | clean: 72 | rm -rf prog.lis 73 | rm -rf prog.out 74 | rm -rf work-obj08.cf 75 | rm -rf $(WAVE) 76 | rm -rf $(ROM) 77 | rm -rf yosys.log 78 | rm -rf $(TOP).tcl 79 | rm -rf post_synth.dcp 80 | rm -rf post_route.dcp 81 | rm -rf $(TOP).bit 82 | rm -rf vivado* 83 | rm -rf usage_statistics_webtalk* 84 | rm -rf tight_setup_hold_pins.txt 85 | rm -rf system.edif 86 | 87 | -------------------------------------------------------------------------------- /cpu/README.md: -------------------------------------------------------------------------------- 1 | # A new implemenation of the QNICE CPU 2 | 3 | ## TODO 4 | * Cleanup code. 5 | * Formal verification. 6 | * Cycle Optimizations (see below). 7 | * Add interrupts. 8 | * Timing optimizations. 9 | 10 | 11 | ## Cycle Optimizations: 12 | 1. Let the FETCH module present two (or three) words to the DECODE module, so the latter doesn't have to wait. 13 | 2. Eliminate the NOP cycle from the CMP @R1, @PC++ instruction. 14 | 3. Optimize conditional jumps, so they don't execute superfluous microoperations. 15 | 4. Optimize FETCH module. It currently takes three clock cycles after a jump. This could be reduced to one clock cycle. 16 | 17 | 18 | 19 | ## Block diagram 20 | ![Block Diagram](cpu.png) 21 | 22 | ## Vivado Synthesis 23 | ``` 24 | Slice LUTs : 921 25 | Slice Registers : 327 26 | Slices : 296 27 | Block RAMs : 2 28 | ``` 29 | 30 | ``` 31 | Number of cells: 5813 32 | $assert 1 33 | BUFG 1 34 | CARRY4 36 35 | FDRE 358 36 | FDSE 2 37 | IBUF 2 38 | INV 96 39 | LUT1 69 40 | LUT2 243 41 | LUT3 245 42 | LUT4 220 43 | LUT5 557 44 | LUT6 1226 45 | MUXF7 1024 46 | MUXF8 173 47 | OBUF 16 48 | RAM128X1D 1024 49 | RAM32M 8 50 | RAM64M 512 51 | 52 | Estimated number of LCs: 2248 53 | ``` 54 | 55 | -------------------------------------------------------------------------------- /cpu/alu_data.vhd: -------------------------------------------------------------------------------- 1 | library ieee; 2 | use ieee.std_logic_1164.all; 3 | use ieee.numeric_std.all; 4 | 5 | use work.cpu_constants.all; 6 | 7 | entity alu_data is 8 | port ( 9 | clk_i : in std_logic; 10 | rst_i : in std_logic; 11 | opcode_i : in std_logic_vector(3 downto 0); 12 | src_data_i : in std_logic_vector(15 downto 0); 13 | dst_data_i : in std_logic_vector(15 downto 0); 14 | sr_i : in std_logic_vector(15 downto 0); 15 | res_data_o : out std_logic_vector(16 downto 0) 16 | ); 17 | end entity alu_data; 18 | 19 | architecture synthesis of alu_data is 20 | 21 | signal res_data : std_logic_vector(16 downto 0); 22 | signal res_shr : std_logic_vector(16 downto 0); 23 | signal res_shl : std_logic_vector(16 downto 0); 24 | 25 | begin 26 | 27 | -- dst << src, fill with X, shift to C 28 | p_shift_left : process (src_data_i, dst_data_i, sr_i) 29 | variable tmp : std_logic_vector(32 downto 0); 30 | variable res : std_logic_vector(16 downto 0); 31 | variable shift : integer; 32 | begin 33 | -- Prepare for shift 34 | tmp(32) := sr_i(C_SR_C); -- Old value of C 35 | tmp(31 downto 16) := dst_data_i; 36 | tmp(15 downto 0) := (15 downto 0 => sr_i(C_SR_X)); -- Fill with X 37 | 38 | shift := to_integer(unsigned(src_data_i)); 39 | if shift <= 16 then 40 | res := tmp(32-shift downto 16-shift); 41 | else 42 | res := (others => sr_i(C_SR_X)); 43 | end if; 44 | 45 | res_shl <= res; 46 | end process p_shift_left; 47 | 48 | 49 | -- dst >> src, fill with C, shift to X 50 | p_shift_right : process (src_data_i, dst_data_i, sr_i) 51 | variable tmp : std_logic_vector(32 downto 0); 52 | variable res : std_logic_vector(16 downto 0); 53 | variable shift : integer; 54 | begin 55 | -- Prepare for shift 56 | tmp(32 downto 17) := (32 downto 17 => sr_i(C_SR_C)); -- Fill with C 57 | tmp(16 downto 1) := dst_data_i; 58 | tmp(0) := sr_i(C_SR_X); -- Old value of X 59 | 60 | shift := to_integer(unsigned(src_data_i)); 61 | if shift <= 16 then 62 | res := tmp(shift+16 downto shift); 63 | else 64 | res := (others => sr_i(C_SR_C)); 65 | end if; 66 | 67 | res_shr <= res; 68 | end process p_shift_right; 69 | 70 | 71 | p_res_data : process (src_data_i, dst_data_i, opcode_i, sr_i, res_shl, res_shr) 72 | begin 73 | res_data <= ("0" & src_data_i); -- Default value to avoid latches 74 | case to_integer(unsigned(opcode_i)) is 75 | when C_OPCODE_MOVE => res_data <= "0" & src_data_i; 76 | when C_OPCODE_ADD => res_data <= std_logic_vector(("0" & unsigned(dst_data_i)) + ("0" & unsigned(src_data_i))); 77 | when C_OPCODE_ADDC => res_data <= std_logic_vector(("0" & unsigned(dst_data_i)) + ("0" & unsigned(src_data_i)) + (X"0000" & sr_i(C_SR_C))); 78 | when C_OPCODE_SUB => res_data <= std_logic_vector(("0" & unsigned(dst_data_i)) - ("0" & unsigned(src_data_i))); 79 | when C_OPCODE_SUBC => res_data <= std_logic_vector(("0" & unsigned(dst_data_i)) - ("0" & unsigned(src_data_i)) - (X"0000" & sr_i(C_SR_C))); 80 | when C_OPCODE_SHL => res_data <= res_shl(16) & (res_shl(15 downto 0)); 81 | when C_OPCODE_SHR => res_data <= res_shr(0) & (res_shr(16 downto 1)); 82 | when C_OPCODE_SWAP => res_data <= "0" & (src_data_i(7 downto 0) & src_data_i(15 downto 8)); 83 | when C_OPCODE_NOT => res_data <= "0" & (not src_data_i); 84 | when C_OPCODE_AND => res_data <= "0" & (dst_data_i and src_data_i); 85 | when C_OPCODE_OR => res_data <= "0" & (dst_data_i or src_data_i); 86 | when C_OPCODE_XOR => res_data <= "0" & (dst_data_i xor src_data_i); 87 | when C_OPCODE_CMP => null; -- TBD 88 | when C_OPCODE_RES => null; -- TBD 89 | when C_OPCODE_CTRL => null; -- TBD 90 | when C_OPCODE_JMP => null; -- TBD 91 | when others => null; 92 | end case; 93 | end process p_res_data; 94 | 95 | res_data_o <= res_data; 96 | 97 | end architecture synthesis; 98 | 99 | -------------------------------------------------------------------------------- /cpu/alu_flags.vhd: -------------------------------------------------------------------------------- 1 | library ieee; 2 | use ieee.std_logic_1164.all; 3 | use ieee.numeric_std.all; 4 | 5 | use work.cpu_constants.all; 6 | 7 | entity alu_flags is 8 | port ( 9 | clk_i : in std_logic; 10 | rst_i : in std_logic; 11 | opcode_i : in std_logic_vector(3 downto 0); 12 | ctrl_i : in std_logic_vector(5 downto 0); 13 | src_data_i : in std_logic_vector(15 downto 0); 14 | dst_data_i : in std_logic_vector(15 downto 0); 15 | sr_i : in std_logic_vector(15 downto 0); 16 | res_data_i : in std_logic_vector(16 downto 0); 17 | sr_o : out std_logic_vector(15 downto 0) 18 | ); 19 | end entity alu_flags; 20 | 21 | architecture synthesis of alu_flags is 22 | 23 | signal cmp_n : std_logic; 24 | signal cmp_v : std_logic; 25 | signal cmp_z : std_logic; 26 | 27 | signal zero : std_logic; 28 | signal carry : std_logic; 29 | signal negative : std_logic; 30 | signal overflow : std_logic; 31 | 32 | begin 33 | 34 | zero <= '1' when res_data_i(15 downto 0) = X"0000" else 35 | '0'; 36 | carry <= res_data_i(16); 37 | negative <= res_data_i(15); 38 | 39 | -- Overflow is true if adding/subtracting two negative numbers yields a positive 40 | -- number or if adding/subtracting two positive numbers yields a negative number 41 | overflow <= (not src_data_i(15) and not dst_data_i(15) and res_data_i(15)) or 42 | ( src_data_i(15) and dst_data_i(15) and not res_data_i(15)); 43 | 44 | cmp_n <= '1' when unsigned(src_data_i) > unsigned(dst_data_i) else 45 | '0'; 46 | 47 | cmp_v <= '1' when signed(src_data_i) > signed(dst_data_i) else 48 | '0'; 49 | 50 | cmp_z <= '1' when src_data_i = dst_data_i else 51 | '0'; 52 | 53 | p_sr : process (all) 54 | begin 55 | sr_o <= sr_i or X"0001"; -- Default value to preserve bits that are not changed. 56 | case to_integer(unsigned(opcode_i)) is 57 | when C_OPCODE_MOVE => sr_o(C_SR_N) <= negative; sr_o(C_SR_Z) <= zero; 58 | when C_OPCODE_ADD => sr_o(C_SR_V) <= overflow; sr_o(C_SR_N) <= negative; sr_o(C_SR_Z) <= zero; sr_o(C_SR_C) <= carry; 59 | when C_OPCODE_ADDC => sr_o(C_SR_V) <= overflow; sr_o(C_SR_N) <= negative; sr_o(C_SR_Z) <= zero; sr_o(C_SR_C) <= carry; 60 | when C_OPCODE_SUB => sr_o(C_SR_V) <= overflow; sr_o(C_SR_N) <= negative; sr_o(C_SR_Z) <= zero; sr_o(C_SR_C) <= carry; 61 | when C_OPCODE_SUBC => sr_o(C_SR_V) <= overflow; sr_o(C_SR_N) <= negative; sr_o(C_SR_Z) <= zero; sr_o(C_SR_C) <= carry; 62 | when C_OPCODE_SHL => sr_o(C_SR_C) <= carry; sr_o(C_SR_N) <= negative; sr_o(C_SR_Z) <= zero; 63 | when C_OPCODE_SHR => sr_o(C_SR_X) <= carry; sr_o(C_SR_N) <= negative; sr_o(C_SR_Z) <= zero; 64 | when C_OPCODE_SWAP => sr_o(C_SR_N) <= negative; sr_o(C_SR_Z) <= zero; 65 | when C_OPCODE_NOT => sr_o(C_SR_N) <= negative; sr_o(C_SR_Z) <= zero; 66 | when C_OPCODE_AND => sr_o(C_SR_N) <= negative; sr_o(C_SR_Z) <= zero; 67 | when C_OPCODE_OR => sr_o(C_SR_N) <= negative; sr_o(C_SR_Z) <= zero; 68 | when C_OPCODE_XOR => sr_o(C_SR_N) <= negative; sr_o(C_SR_Z) <= zero; 69 | when C_OPCODE_CMP => sr_o(C_SR_V) <= cmp_v; sr_o(C_SR_N) <= cmp_n; sr_o(C_SR_Z) <= cmp_z; 70 | when C_OPCODE_RES => null; -- No status bits are changed 71 | when C_OPCODE_CTRL => 72 | case to_integer(unsigned(ctrl_i)) is 73 | when C_CTRL_INCRB => sr_o <= std_logic_vector(unsigned(sr_i or X"0001") + X"0100"); 74 | when C_CTRL_DECRB => sr_o <= std_logic_vector(unsigned(sr_i or X"0001") - X"0100"); 75 | when others => null; 76 | end case; 77 | when C_OPCODE_JMP => null; -- No status bits are changed 78 | when others => null; 79 | end case; 80 | end process p_sr; 81 | 82 | end architecture synthesis; 83 | 84 | -------------------------------------------------------------------------------- /cpu/axi_pause.vhd: -------------------------------------------------------------------------------- 1 | ------------------------------------------------------------------------------- 2 | -- Description: 3 | -- This module generates empty cycles in an AXI stream by deasserting 4 | -- m_tready_o and s_tvalid_o at regular intervals. The period between the empty 5 | -- cycles can be controlled by the generic G_PAUSE_SIZE: 6 | -- * Setting it to 0 disables the empty cycles. 7 | -- * Setting it to 10 inserts empty cycles every tenth cycle, i.e. 90 % throughput. 8 | -- * Setting it to -10 inserts empty cycles except every tenth cycle, i.e. 10 % throughput. 9 | -- * Etc. 10 | ------------------------------------------------------------------------------- 11 | 12 | library ieee; 13 | use ieee.std_logic_1164.all; 14 | use ieee.numeric_std_unsigned.all; 15 | 16 | entity axi_pause is 17 | generic ( 18 | G_TDATA_SIZE : integer := 16; 19 | G_PAUSE_SIZE : integer 20 | ); 21 | port ( 22 | clk_i : in std_logic; 23 | rst_i : in std_logic; 24 | 25 | -- Input 26 | s_tvalid_i : in std_logic; 27 | s_tready_o : out std_logic; 28 | s_tdata_i : in std_logic_vector(G_TDATA_SIZE-1 downto 0); 29 | 30 | -- Output 31 | m_tvalid_o : out std_logic; 32 | m_tready_i : in std_logic; 33 | m_tdata_o : out std_logic_vector(G_TDATA_SIZE-1 downto 0) 34 | ); 35 | end entity axi_pause; 36 | 37 | architecture simulation of axi_pause is 38 | 39 | signal cnt_r : integer range 0 to abs(G_PAUSE_SIZE) := 0; 40 | 41 | begin 42 | 43 | cnt_proc : process (clk_i) 44 | begin 45 | if rising_edge(clk_i) then 46 | if G_PAUSE_SIZE /= 0 then 47 | -- Generate a value in range 1 to G_PAUSE_SIZE 48 | if (cnt_r = G_PAUSE_SIZE - 1) and (m_tvalid_o = '1') and (m_tready_i = '0') then 49 | -- If offering data which is not taken, do not change the valid 50 | -- signal, until data has been accepted. 51 | cnt_r <= cnt_r; 52 | else 53 | cnt_r <= (cnt_r + 1) mod abs(G_PAUSE_SIZE); 54 | end if; 55 | end if; 56 | end if; 57 | end process cnt_proc; 58 | 59 | no_pause_gen : if G_PAUSE_SIZE = 0 generate 60 | s_tready_o <= m_tready_i; 61 | m_tvalid_o <= s_tvalid_i; 62 | end generate no_pause_gen; 63 | 64 | pause_positive_gen : if G_PAUSE_SIZE > 0 generate 65 | -- Insert empty cycle when cnt_r reaches zero. 66 | s_tready_o <= '0' when cnt_r = 0 else 67 | m_tready_i; 68 | m_tvalid_o <= '0' when cnt_r = 0 else 69 | s_tvalid_i; 70 | end generate pause_positive_gen; 71 | 72 | pause_negative_gen : if G_PAUSE_SIZE < 0 generate 73 | -- Insert empty cycle except when cnt_r reaches zero. 74 | s_tready_o <= '0' when cnt_r /= 0 else 75 | m_tready_i; 76 | m_tvalid_o <= '0' when cnt_r /= 0 else 77 | s_tvalid_i; 78 | end generate pause_negative_gen; 79 | 80 | m_tdata_o <= s_tdata_i; 81 | 82 | end architecture simulation; 83 | 84 | -------------------------------------------------------------------------------- /cpu/cpu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MJoergen/formal/9e0bc6c20361925199b3a94eb3dd362aba29b010/cpu/cpu.png -------------------------------------------------------------------------------- /cpu/dp_ram.vhd: -------------------------------------------------------------------------------- 1 | library ieee; 2 | use ieee.std_logic_1164.all; 3 | use ieee.numeric_std_unsigned.all; 4 | 5 | entity dp_ram is 6 | generic ( 7 | G_RAM_STYLE : string := "block"; 8 | G_ADDR_SIZE : integer; 9 | G_DATA_SIZE : integer 10 | ); 11 | port ( 12 | clk_i : in std_logic; 13 | rst_i : in std_logic; 14 | -- Write interface 15 | wr_addr_i : in std_logic_vector(G_ADDR_SIZE-1 downto 0); 16 | wr_data_i : in std_logic_vector(G_DATA_SIZE-1 downto 0); 17 | wr_en_i : in std_logic; 18 | -- Read interface 19 | rd_addr_i : in std_logic_vector(G_ADDR_SIZE-1 downto 0); 20 | rd_data_o : out std_logic_vector(G_DATA_SIZE-1 downto 0) 21 | ); 22 | end entity dp_ram; 23 | 24 | architecture synthesis of dp_ram is 25 | 26 | type mem_t is array (0 to 2**G_ADDR_SIZE-1) of std_logic_vector(G_DATA_SIZE-1 downto 0); 27 | 28 | signal ram_r : mem_t := (others => (others => '0')); 29 | 30 | attribute ram_style : string; 31 | attribute ram_style of ram_r : signal is G_RAM_STYLE; 32 | 33 | begin 34 | 35 | p_write : process (clk_i) 36 | begin 37 | if rising_edge(clk_i) then 38 | if wr_en_i = '1' then 39 | ram_r(to_integer(wr_addr_i)) <= wr_data_i; 40 | end if; 41 | 42 | -- pragma synthesis_off 43 | if rst_i = '1' then 44 | for i in 0 to 7 loop 45 | ram_r(i) <= X"111" * to_std_logic_vector(i, 4); 46 | end loop; 47 | end if; 48 | -- pragma synthesis_on 49 | end if; 50 | end process p_write; 51 | 52 | 53 | p_read : process (clk_i) 54 | begin 55 | if rising_edge(clk_i) then 56 | rd_data_o <= ram_r(to_integer(rd_addr_i)); 57 | end if; 58 | end process p_read; 59 | 60 | end architecture synthesis; 61 | 62 | -------------------------------------------------------------------------------- /cpu/microcode.vhd: -------------------------------------------------------------------------------- 1 | library ieee; 2 | use ieee.std_logic_1164.all; 3 | use ieee.numeric_std_unsigned.all; 4 | 5 | -- address bitmap: 6 | -- bit 5 : read from dst 7 | -- bit 4 : write to dst 8 | -- bit 3 : src mem 9 | -- bit 2 : dst mem 10 | -- bits 1-0 : count 11 | -- 12 | -- value bitmap 13 | -- bit 6 : last 14 | -- bit 5 : mem to alu src 15 | -- bit 4 : mem to alu dst 16 | -- bit 3 : mem read to src 17 | -- bit 2 : mem read to dst 18 | -- bit 1 : mem write 19 | -- bit 0 : reg write 20 | 21 | entity microcode is 22 | port ( 23 | addr_i : in std_logic_vector(5 downto 0); 24 | value_o : out std_logic_vector(8 downto 0) 25 | ); 26 | end entity microcode; 27 | 28 | architecture synthesis of microcode is 29 | 30 | constant C_LAST : std_logic_vector(8 downto 0) := "100000000"; 31 | constant C_REG_MOD_SRC : std_logic_vector(8 downto 0) := "010000000"; 32 | constant C_REG_MOD_DST : std_logic_vector(8 downto 0) := "001000000"; 33 | constant C_MEM_ALU_SRC : std_logic_vector(8 downto 0) := "000100000"; 34 | constant C_MEM_ALU_DST : std_logic_vector(8 downto 0) := "000010000"; 35 | constant C_MEM_READ_SRC : std_logic_vector(8 downto 0) := "000001000"; 36 | constant C_MEM_READ_DST : std_logic_vector(8 downto 0) := "000000100"; 37 | constant C_MEM_WRITE : std_logic_vector(8 downto 0) := "000000010"; 38 | constant C_REG_WRITE : std_logic_vector(8 downto 0) := "000000001"; 39 | 40 | type microcode_t is array (0 to 63) of std_logic_vector(8 downto 0); 41 | constant C_MICROCODE : microcode_t := ( 42 | -- JMP R, R 43 | C_LAST, 44 | C_LAST, 45 | C_LAST, 46 | C_LAST, 47 | 48 | -- JMP R, @R 49 | C_LAST, 50 | C_LAST, 51 | C_LAST, 52 | C_LAST, 53 | 54 | -- JMP @R, R 55 | C_MEM_READ_SRC or C_REG_MOD_SRC, 56 | C_LAST or C_MEM_ALU_SRC, 57 | C_LAST, 58 | C_LAST, 59 | 60 | -- JMP @R, @R 61 | C_MEM_READ_SRC or C_REG_MOD_SRC, 62 | C_LAST or C_MEM_ALU_SRC, 63 | C_LAST, 64 | C_LAST, 65 | 66 | -- MOVE R, R 67 | C_LAST or C_REG_WRITE, 68 | C_LAST, 69 | C_LAST, 70 | C_LAST, 71 | 72 | -- MOVE R, @R 73 | C_LAST or C_MEM_WRITE or C_REG_MOD_DST, 74 | C_LAST, 75 | C_LAST, 76 | C_LAST, 77 | 78 | -- MOVE @R, R 79 | C_MEM_READ_SRC or C_REG_MOD_SRC, 80 | C_LAST or C_MEM_ALU_SRC or C_REG_WRITE, 81 | C_LAST, 82 | C_LAST, 83 | 84 | -- MOVE @R, @R 85 | C_MEM_READ_SRC or C_REG_MOD_SRC, 86 | C_LAST or C_MEM_ALU_SRC or C_MEM_WRITE or C_REG_MOD_DST, 87 | C_LAST, 88 | C_LAST, 89 | 90 | -- CMP R, R 91 | C_LAST, 92 | C_LAST, 93 | C_LAST, 94 | C_LAST, 95 | 96 | -- CMP R, @R 97 | C_MEM_READ_DST or C_REG_MOD_DST, 98 | C_LAST or C_MEM_ALU_DST, 99 | C_LAST, 100 | C_LAST, 101 | 102 | -- CMP @R, R 103 | C_MEM_READ_SRC or C_REG_MOD_SRC, 104 | C_LAST or C_MEM_ALU_SRC, 105 | C_LAST, 106 | C_LAST, 107 | 108 | -- CMP @R, @R 109 | C_MEM_READ_SRC or C_REG_MOD_SRC, 110 | C_MEM_READ_DST or C_REG_MOD_DST, 111 | C_LAST or C_MEM_ALU_SRC or C_MEM_ALU_DST, 112 | C_LAST, 113 | 114 | -- ADD R, R 115 | C_LAST or C_REG_WRITE, 116 | C_LAST, 117 | C_LAST, 118 | C_LAST, 119 | 120 | -- ADD R, @R 121 | C_MEM_READ_DST, 122 | C_LAST or C_MEM_ALU_DST or C_MEM_WRITE or C_REG_MOD_DST, 123 | C_LAST, 124 | C_LAST, 125 | 126 | -- ADD @R, R 127 | C_MEM_READ_SRC or C_REG_MOD_SRC, 128 | C_LAST or C_MEM_ALU_SRC or C_REG_WRITE, 129 | C_LAST, 130 | C_LAST, 131 | 132 | -- ADD @R, @R 133 | C_MEM_READ_SRC or C_REG_MOD_SRC, 134 | C_MEM_READ_DST, 135 | C_LAST or C_MEM_ALU_SRC or C_MEM_ALU_DST or C_MEM_WRITE or C_REG_MOD_DST, 136 | C_LAST 137 | ); -- constant C_MICROCODE : microcode_t := ( 138 | 139 | begin 140 | 141 | value_o <= C_MICROCODE(to_integer(addr_i)); 142 | 143 | end architecture synthesis; 144 | 145 | -------------------------------------------------------------------------------- /cpu/prog_simple.asm: -------------------------------------------------------------------------------- 1 | MOVE R2, R6 ; Write value 0x0222 to register 6 2 | MOVE R3, @R6 ; Write value 0x0333 to memory 0x0222 3 | MOVE @R6, R4 ; Write value 0x0333 to register 4 4 | MOVE @R6, @R7 ; Write value 0x0333 to memory 0x0777 5 | 6 | ADD R2, R9 ; Write value 0x0BBB to register 9 7 | ADD R3, @R9 ; Write value 0x0333 to memory 0x0BBB 8 | ADD @R9, R8 ; Write value 0x0BBB to register 8 9 | ADD @R8, @R7 ; Write value 0x0666 to memory 0x0777 10 | 11 | CMP R2, R8 12 | CMP R3, @R8 13 | CMP @R4, R8 14 | CMP @R5, @R8 15 | 16 | MOVE L_4, R13 ; Initialize stack pointer 17 | RSUB L_3, 1 18 | 19 | MOVE 0x1234, R1 ; Write value 0x1234 to register 1 20 | ADD 0x2345, R1 ; Write value 0x3579 to register 1 21 | 22 | CMP 0x1234, R1 23 | CMP R1, 0x2345 24 | 25 | CMP 0x1234, @R1 26 | CMP @R1, 0x2345 27 | 28 | ABRA L_1, 1 29 | HALT 30 | 31 | L_1 RBRA L_2, 1 32 | HALT 33 | 34 | L_2 HALT 35 | 36 | L_3 MOVE @R13++, R15 37 | .DW 0x0000 38 | .DW 0x0000 39 | .DW 0x0000 40 | .DW 0x0000 41 | L_4 42 | -------------------------------------------------------------------------------- /cpu/registers.vhd: -------------------------------------------------------------------------------- 1 | library ieee; 2 | use ieee.std_logic_1164.all; 3 | use ieee.numeric_std_unsigned.all; 4 | 5 | use work.cpu_constants.all; 6 | 7 | entity registers is 8 | port ( 9 | clk_i : in std_logic; 10 | rst_i : in std_logic; 11 | -- Read interface 12 | src_reg_i : in std_logic_vector(3 downto 0); 13 | src_val_o : out std_logic_vector(15 downto 0); 14 | dst_reg_i : in std_logic_vector(3 downto 0); 15 | dst_val_o : out std_logic_vector(15 downto 0); 16 | r14_o : out std_logic_vector(15 downto 0); 17 | -- Write interface 18 | r14_we_i : in std_logic; 19 | r14_i : in std_logic_vector(15 downto 0); 20 | reg_we_i : in std_logic; 21 | reg_addr_i : in std_logic_vector(3 downto 0); 22 | reg_val_i : in std_logic_vector(15 downto 0) 23 | ); 24 | end entity registers; 25 | 26 | architecture synthesis of registers is 27 | 28 | type upper_mem_t is array (8 to 15) of std_logic_vector(15 downto 0); 29 | 30 | signal upper_regs : upper_mem_t := (others => (others => '0')); 31 | 32 | signal r14 : std_logic_vector(15 downto 0) := (others => '0'); 33 | 34 | signal src_val_upper : std_logic_vector(15 downto 0); 35 | signal dst_val_upper : std_logic_vector(15 downto 0); 36 | 37 | signal src_val_lower : std_logic_vector(15 downto 0); 38 | signal dst_val_lower : std_logic_vector(15 downto 0); 39 | 40 | signal src_reg_r : std_logic_vector(3 downto 0); 41 | signal dst_reg_r : std_logic_vector(3 downto 0); 42 | 43 | signal src_rd_addr : std_logic_vector(10 downto 0); 44 | signal dst_rd_addr : std_logic_vector(10 downto 0); 45 | signal wr_addr : std_logic_vector(10 downto 0); 46 | 47 | begin 48 | 49 | src_rd_addr <= r14(15 downto 8) & src_reg_i(2 downto 0); 50 | dst_rd_addr <= r14(15 downto 8) & dst_reg_i(2 downto 0); 51 | wr_addr <= r14(15 downto 8) & reg_addr_i(2 downto 0); 52 | 53 | 54 | i_ram_lower_src : entity work.dp_ram 55 | generic map ( 56 | G_ADDR_SIZE => 11, 57 | G_DATA_SIZE => 16 58 | ) 59 | port map ( 60 | clk_i => clk_i, 61 | rst_i => rst_i, 62 | rd_addr_i => src_rd_addr, 63 | rd_data_o => src_val_lower, 64 | wr_addr_i => wr_addr, 65 | wr_data_i => reg_val_i, 66 | wr_en_i => reg_we_i and not reg_addr_i(3) 67 | ); -- i_ram_lower_src 68 | 69 | 70 | i_ram_lower_dst : entity work.dp_ram 71 | generic map ( 72 | G_ADDR_SIZE => 11, 73 | G_DATA_SIZE => 16 74 | ) 75 | port map ( 76 | clk_i => clk_i, 77 | rst_i => rst_i, 78 | rd_addr_i => dst_rd_addr, 79 | rd_data_o => dst_val_lower, 80 | wr_addr_i => wr_addr, 81 | wr_data_i => reg_val_i, 82 | wr_en_i => reg_we_i and not reg_addr_i(3) 83 | ); -- i_ram_lower_dst 84 | 85 | 86 | p_write : process (clk_i) 87 | begin 88 | if rising_edge(clk_i) then 89 | if reg_we_i = '1' then 90 | if to_integer(reg_addr_i) >= 8 then 91 | upper_regs(to_integer(reg_addr_i)) <= reg_val_i; 92 | end if; 93 | end if; 94 | 95 | -- pragma synthesis_off 96 | if rst_i = '1' then 97 | for i in 8 to 15 loop 98 | upper_regs(i) <= X"111" * to_std_logic_vector(i, 4); 99 | end loop; 100 | end if; 101 | -- pragma synthesis_on 102 | end if; 103 | end process p_write; 104 | 105 | 106 | p_delay : process (clk_i) 107 | begin 108 | if rising_edge(clk_i) then 109 | src_reg_r <= src_reg_i; 110 | dst_reg_r <= dst_reg_i; 111 | end if; 112 | end process p_delay; 113 | 114 | p_read : process (clk_i) 115 | begin 116 | if rising_edge(clk_i) then 117 | src_val_upper <= upper_regs(8+to_integer(src_reg_i(2 downto 0))); 118 | dst_val_upper <= upper_regs(8+to_integer(dst_reg_i(2 downto 0))); 119 | end if; 120 | end process p_read; 121 | 122 | 123 | p_r14 : process (clk_i) 124 | begin 125 | if rising_edge(clk_i) then 126 | if r14_we_i = '1' then 127 | r14 <= r14_i or X"0001"; 128 | end if; 129 | 130 | if reg_we_i = '1' and reg_addr_i = C_REG_SR then 131 | r14 <= reg_val_i or X"0001"; 132 | end if; 133 | 134 | if rst_i = '1' then 135 | r14 <= X"0001"; 136 | end if; 137 | end if; 138 | end process p_r14; 139 | 140 | 141 | r14_o <= r14; 142 | 143 | src_val_o <= r14 when src_reg_r = C_REG_SR else 144 | src_val_upper when src_reg_r >= 8 else 145 | src_val_lower; 146 | dst_val_o <= r14 when dst_reg_r = C_REG_SR else 147 | dst_val_upper when dst_reg_r >= 8 else 148 | dst_val_lower; 149 | 150 | end architecture synthesis; 151 | 152 | -------------------------------------------------------------------------------- /cpu/system.vhd: -------------------------------------------------------------------------------- 1 | library ieee; 2 | use ieee.std_logic_1164.all; 3 | 4 | entity system is 5 | port ( 6 | clk_i : in std_logic; 7 | rstn_i : in std_logic; 8 | led_o : out std_logic_vector(15 downto 0) 9 | ); 10 | end entity system; 11 | 12 | architecture synthesis of system is 13 | 14 | signal wbi_cyc : std_logic; 15 | signal wbi_stb : std_logic; 16 | signal wbi_stall : std_logic; 17 | signal wbi_addr : std_logic_vector(15 downto 0); 18 | signal wbi_ack : std_logic; 19 | signal wbi_data_rd : std_logic_vector(15 downto 0); 20 | signal wbd_cyc : std_logic; 21 | signal wbd_stb : std_logic; 22 | signal wbd_stall : std_logic; 23 | signal wbd_addr : std_logic_vector(15 downto 0); 24 | signal wbd_we : std_logic; 25 | signal wbd_data_wr : std_logic_vector(15 downto 0); 26 | signal wbd_ack : std_logic; 27 | signal wbd_data_rd : std_logic_vector(15 downto 0); 28 | 29 | begin 30 | 31 | led_o <= wbd_addr; 32 | 33 | i_cpu : entity work.cpu 34 | port map ( 35 | clk_i => clk_i, 36 | rst_i => not rstn_i, 37 | wbi_cyc_o => wbi_cyc, 38 | wbi_stb_o => wbi_stb, 39 | wbi_stall_i => wbi_stall, 40 | wbi_addr_o => wbi_addr, 41 | wbi_ack_i => wbi_ack, 42 | wbi_data_i => wbi_data_rd, 43 | wbd_cyc_o => wbd_cyc, 44 | wbd_stb_o => wbd_stb, 45 | wbd_stall_i => wbd_stall, 46 | wbd_addr_o => wbd_addr, 47 | wbd_we_o => wbd_we, 48 | wbd_dat_o => wbd_data_wr, 49 | wbd_ack_i => wbd_ack, 50 | wbd_data_i => wbd_data_rd 51 | ); -- i_cpu 52 | 53 | i_tdp_mem : entity work.wb_tdp_mem 54 | generic map ( 55 | G_INIT_FILE => "../cpu/prog.rom", 56 | G_RAM_STYLE => "block", 57 | G_ADDR_SIZE => 13, 58 | G_DATA_SIZE => 16 59 | ) 60 | port map ( 61 | clk_i => clk_i, 62 | rst_i => not rstn_i, 63 | wb_a_cyc_i => wbi_cyc, 64 | wb_a_stb_i => wbi_stb, 65 | wb_a_stall_o => wbi_stall, 66 | wb_a_addr_i => wbi_addr(12 downto 0), 67 | wb_a_we_i => '0', 68 | wb_a_data_i => X"0000", 69 | wb_a_ack_o => wbi_ack, 70 | wb_a_data_o => wbi_data_rd, 71 | wb_b_cyc_i => wbd_cyc, 72 | wb_b_stb_i => wbd_stb, 73 | wb_b_stall_o => wbd_stall, 74 | wb_b_addr_i => wbd_addr(12 downto 0), 75 | wb_b_we_i => wbd_we, 76 | wb_b_data_i => wbd_data_wr, 77 | wb_b_ack_o => wbd_ack, 78 | wb_b_data_o => wbd_data_rd 79 | ); -- i_tdp_mem 80 | 81 | end architecture synthesis; 82 | 83 | -------------------------------------------------------------------------------- /cpu/system.xdc: -------------------------------------------------------------------------------- 1 | # This file is specific for the Nexys 4 DDR board. 2 | 3 | # Clock and reset 4 | set_property -dict { PACKAGE_PIN E3 IOSTANDARD LVCMOS33 } [get_ports { clk_i }]; # CLK100MHZ 5 | set_property -dict { PACKAGE_PIN C12 IOSTANDARD LVCMOS33 } [get_ports { rstn_i }]; # CPU_RESETN 6 | 7 | # LEDs 8 | set_property -dict { PACKAGE_PIN H17 IOSTANDARD LVCMOS33 } [get_ports { led_o[0] }]; # LED0 9 | set_property -dict { PACKAGE_PIN K15 IOSTANDARD LVCMOS33 } [get_ports { led_o[1] }]; # LED1 10 | set_property -dict { PACKAGE_PIN J13 IOSTANDARD LVCMOS33 } [get_ports { led_o[2] }]; # LED2 11 | set_property -dict { PACKAGE_PIN N14 IOSTANDARD LVCMOS33 } [get_ports { led_o[3] }]; # LED3 12 | set_property -dict { PACKAGE_PIN R18 IOSTANDARD LVCMOS33 } [get_ports { led_o[4] }]; # LED4 13 | set_property -dict { PACKAGE_PIN V17 IOSTANDARD LVCMOS33 } [get_ports { led_o[5] }]; # LED5 14 | set_property -dict { PACKAGE_PIN U17 IOSTANDARD LVCMOS33 } [get_ports { led_o[6] }]; # LED6 15 | set_property -dict { PACKAGE_PIN U16 IOSTANDARD LVCMOS33 } [get_ports { led_o[7] }]; # LED7 16 | set_property -dict { PACKAGE_PIN V16 IOSTANDARD LVCMOS33 } [get_ports { led_o[8] }]; # LED8 17 | set_property -dict { PACKAGE_PIN T15 IOSTANDARD LVCMOS33 } [get_ports { led_o[9] }]; # LED9 18 | set_property -dict { PACKAGE_PIN U14 IOSTANDARD LVCMOS33 } [get_ports { led_o[10] }]; # LED10 19 | set_property -dict { PACKAGE_PIN T16 IOSTANDARD LVCMOS33 } [get_ports { led_o[11] }]; # LED11 20 | set_property -dict { PACKAGE_PIN V15 IOSTANDARD LVCMOS33 } [get_ports { led_o[12] }]; # LED12 21 | set_property -dict { PACKAGE_PIN V14 IOSTANDARD LVCMOS33 } [get_ports { led_o[13] }]; # LED13 22 | set_property -dict { PACKAGE_PIN V12 IOSTANDARD LVCMOS33 } [get_ports { led_o[14] }]; # LED14 23 | set_property -dict { PACKAGE_PIN V11 IOSTANDARD LVCMOS33 } [get_ports { led_o[15] }]; # LED15 24 | 25 | # Clock definition 26 | create_clock -name sys_clk -period 14.50 [get_ports {clk_i}]; 27 | 28 | # Configuration Bank Voltage Select 29 | set_property CFGBVS VCCO [current_design] 30 | set_property CONFIG_VOLTAGE 3.3 [current_design] 31 | 32 | -------------------------------------------------------------------------------- /cpu/tb_cpu.vhd: -------------------------------------------------------------------------------- 1 | library ieee; 2 | use ieee.std_logic_1164.all; 3 | 4 | entity tb_cpu is 5 | end entity tb_cpu; 6 | 7 | architecture simulation of tb_cpu is 8 | 9 | signal clk : std_logic; 10 | signal rstn : std_logic; 11 | 12 | begin 13 | 14 | p_clk : process 15 | begin 16 | clk <= '1', '0' after 5 ns; 17 | wait for 10 ns; -- 100 MHz 18 | end process p_clk; 19 | 20 | p_rstn : process 21 | begin 22 | rstn <= '0'; 23 | wait for 100 ns; 24 | wait until clk = '1'; 25 | rstn <= '1'; 26 | wait; 27 | end process p_rstn; 28 | 29 | i_system : entity work.system 30 | port map ( 31 | clk_i => clk, 32 | rstn_i => rstn 33 | ); -- i_cpu 34 | 35 | end architecture simulation; 36 | 37 | -------------------------------------------------------------------------------- /fetch/Makefile: -------------------------------------------------------------------------------- 1 | # Type 'make formal' to run formal verification 2 | # Type 'make synth' to run synthesis 3 | 4 | DUT = fetch 5 | SRC += ../one_stage_buffer/one_stage_buffer.vhd 6 | SRC += $(DUT).vhd 7 | 8 | 9 | ####################### 10 | # Formal verification 11 | ####################### 12 | 13 | .PHONY: formal 14 | formal: $(DUT)_cover/PASS $(DUT)_prove/PASS 15 | $(DUT)_cover/PASS: $(DUT).sby $(DUT).psl $(SRC) 16 | # This is the main command line to run the formal verification 17 | sby --yosys "yosys -m ghdl" -f $(DUT).sby 18 | 19 | show_prove: 20 | gtkwave $(DUT)_prove/engine_0/trace_induct.vcd $(DUT).gtkw 21 | 22 | 23 | ####################### 24 | # Synthesis 25 | ####################### 26 | 27 | .PHONY: synth 28 | synth: work-obj08.cf 29 | yosys -m ghdl -p 'ghdl -fpsl -fsynopsys --std=08 $(DUT); synth_xilinx -top $(DUT) -edif $(DUT).edif' > yosys.log 30 | 31 | work-obj08.cf: $(SRC) 32 | ghdl -a -fpsl -fsynopsys --std=08 $^ 33 | 34 | 35 | ####################### 36 | # Cleanup 37 | ####################### 38 | 39 | .PHONY: clean 40 | clean: 41 | rm -rf $(DUT)_cover/ 42 | rm -rf $(DUT)_prove/ 43 | rm -rf work-obj08.cf 44 | rm -rf yosys.log 45 | rm -rf $(DUT).edif 46 | 47 | -------------------------------------------------------------------------------- /fetch/fetch.gtkw: -------------------------------------------------------------------------------- 1 | [*] 2 | [*] GTKWave Analyzer v3.3.103 (w)1999-2019 BSI 3 | [*] Wed Dec 30 11:42:18 2020 4 | [*] 5 | [dumpfile] "/home/mike/git/MJoergen/formal/fetch/fetch_cover/engine_0/trace8.vcd" 6 | [dumpfile_mtime] "Wed Dec 30 11:41:46 2020" 7 | [dumpfile_size] 6030 8 | [savefile] "/home/mike/git/MJoergen/formal/fetch/fetch.gtkw" 9 | [timestart] 0 10 | [size] 1834 1011 11 | [pos] 18 0 12 | *-4.486220 116 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 13 | [treeopen] fetch. 14 | [sst_width] 249 15 | [signals_width] 338 16 | [sst_expanded] 1 17 | [sst_vpaned_height] 286 18 | @28 19 | fetch.clk_i 20 | fetch.rst_i 21 | @200 22 | - 23 | @28 24 | [color] 2 25 | fetch.dc_valid_i 26 | @22 27 | fetch.dc_addr_i[15:0] 28 | @200 29 | - 30 | @28 31 | [color] 2 32 | fetch.wb_cyc_o 33 | [color] 2 34 | fetch.wb_stb_o 35 | [color] 7 36 | fetch.wb_stall_i 37 | @22 38 | fetch.wb_addr_o[15:0] 39 | @200 40 | - 41 | @28 42 | [color] 2 43 | fetch.wb_ack_i 44 | @22 45 | fetch.wb_data_i[15:0] 46 | @200 47 | - 48 | @800200 49 | -one_stage_buffer 50 | @28 51 | [color] 2 52 | fetch.i_one_stage_buffer.s_valid_i 53 | [color] 7 54 | fetch.i_one_stage_buffer.s_ready_o 55 | @22 56 | fetch.i_one_stage_buffer.s_data_i[31:0] 57 | @28 58 | [color] 2 59 | fetch.i_one_stage_buffer.m_valid_o 60 | [color] 7 61 | fetch.i_one_stage_buffer.m_ready_i 62 | @22 63 | fetch.i_one_stage_buffer.m_data_o[31:0] 64 | @1000200 65 | -one_stage_buffer 66 | @200 67 | - 68 | @28 69 | [color] 2 70 | fetch.dc_valid_o 71 | [color] 7 72 | fetch.dc_ready_i 73 | @22 74 | fetch.dc_addr_o[15:0] 75 | fetch.dc_data_o[15:0] 76 | @200 77 | - 78 | @25 79 | fetch.formal_gen.f_wb_req_count[1:0] 80 | @24 81 | fetch.formal_gen.f_wb_stall_delay[1:0] 82 | fetch.formal_gen.f_wb_ack_delay[1:0] 83 | @22 84 | fetch.formal_gen.f_wb_addr[15:0] 85 | @24 86 | fetch.formal_gen.f_dc_stall_delay[1:0] 87 | [pattern_trace] 1 88 | [pattern_trace] 0 89 | -------------------------------------------------------------------------------- /fetch/fetch.psl: -------------------------------------------------------------------------------- 1 | vunit i_fetch(fetch(synthesis)) 2 | { 3 | 4 | signal f_wb_req_count : integer range 0 to 3 := 0; 5 | signal f_wb_stall_delay : integer range 0 to 3 := 0; 6 | signal f_wb_ack_delay : integer range 0 to 3 := 0; 7 | signal f_wb_addr : std_logic_vector(15 downto 0) := (others => '0'); 8 | signal f_dc_stall_delay : integer range 0 to 3 := 0; 9 | signal f_dc_last_addr_valid : std_logic := '0'; 10 | signal f_dc_last_addr : std_logic_vector(15 downto 0) := (others => '0'); 11 | 12 | -- set all declarations to run on clk 13 | default clock is rising_edge(clk_i); 14 | 15 | 16 | ------------------------------------------------ 17 | -- PROPERTIES OF THE WISHBONE MASTER INTERFACE 18 | ------------------------------------------------ 19 | 20 | -- Count the number of outstanding WISHBONE requests 21 | p_wb_req_count : process (clk_i) 22 | begin 23 | if rising_edge(clk_i) then 24 | -- Request without response 25 | if wb_cyc_o and wb_stb_o and not wb_stall_i and not (wb_cyc_o and wb_ack_i) then 26 | f_wb_req_count <= f_wb_req_count + 1; 27 | end if; 28 | 29 | -- Reponse without request 30 | if not(wb_cyc_o and wb_stb_o and not wb_stall_i) and (wb_cyc_o and wb_ack_i) then 31 | f_wb_req_count <= f_wb_req_count - 1; 32 | end if; 33 | 34 | -- If CYC goes low mid-transaction, the transaction is aborted. 35 | if rst_i or not wb_cyc_o then 36 | f_wb_req_count <= 0; 37 | end if; 38 | end if; 39 | end process p_wb_req_count; 40 | 41 | -- Count the number of clock cycles the WISHBONE SLAVE stalls 42 | p_wb_stall_delay : process (clk_i) 43 | begin 44 | if rising_edge(clk_i) then 45 | -- Stalled request 46 | if wb_cyc_o and wb_stb_o and wb_stall_i then 47 | f_wb_stall_delay <= f_wb_stall_delay + 1; 48 | else 49 | f_wb_stall_delay <= 0; 50 | end if; 51 | end if; 52 | end process p_wb_stall_delay; 53 | 54 | -- Count the number of clock cycles the WISHBONE SLAVE waits before responding 55 | p_wb_ack_delay : process (clk_i) 56 | begin 57 | if rising_edge(clk_i) then 58 | -- Transaction without response 59 | if (f_wb_req_count > 0 or (wb_cyc_o = '1' and wb_stb_o = '1' and wb_stall_i = '0')) and wb_cyc_o = '1' and wb_ack_i = '0' then 60 | f_wb_ack_delay <= f_wb_ack_delay + 1; 61 | else 62 | f_wb_ack_delay <= 0; 63 | end if; 64 | end if; 65 | end process p_wb_ack_delay; 66 | 67 | -- WISHBONE MASTER: At most one outstanding request 68 | f_wb_req_count_max : assert always {f_wb_req_count >= 1} |-> {not wb_stb_o}; 69 | 70 | -- WISHBONE SLAVE: Only stall for at most 2 clock cycles. This is an artifical constraint. 71 | f_wb_stall_delay_max : assume always {f_wb_stall_delay <= 2}; 72 | 73 | -- WISHBONE SLAVE: Respond within at most 2 clock cycles. This is an artifical constraint. 74 | f_wb_ack_delay_max : assume always {f_wb_ack_delay <= 2}; 75 | 76 | -- WISHBONE MASTER: STB must be low when CYC is low. 77 | f_stb_low : assert always {not wb_cyc_o} |-> {not wb_stb_o}; 78 | 79 | -- WISHBONE SLAVE: No ACKs without CYC 80 | f_wb_ack_cyc : assume always {not wb_cyc_o} |=> {not wb_ack_i}; 81 | 82 | -- WISHBONE MASTER: While a request is stalled it cannot change, except on reset or abort. 83 | f_wb_stable : assert always {wb_stb_o and wb_stall_i and not dc_valid_i and not rst_i} |=> {stable(wb_stb_o) and stable(wb_addr_o)}; 84 | 85 | -- WISHBONE SLAVE: Only ACK an outstanding request 86 | f_wb_ack_pending : assume always {wb_ack_i} |-> {f_wb_req_count > 0}; 87 | 88 | 89 | -- Keep track of addresses expected to be requested on the WISHBONE bus 90 | p_wb_addr : process (clk_i) 91 | begin 92 | if rising_edge(clk_i) then 93 | if wb_cyc_o = '1' and wb_stb_o = '1' and wb_stall_i = '0' then 94 | f_wb_addr <= f_wb_addr + 1; 95 | end if; 96 | 97 | if dc_valid_i = '1' then 98 | f_wb_addr <= dc_addr_i; 99 | end if; 100 | end if; 101 | end process p_wb_addr; 102 | 103 | -- Verify address requested on WISHBONE bus is as expected 104 | f_wb_address : assert always {wb_cyc_o and wb_stb_o} |-> wb_addr_o = f_wb_addr; 105 | 106 | 107 | --------------------------------------- 108 | -- PROPERTIES OF THE DECODE INTERFACE 109 | --------------------------------------- 110 | 111 | -- Count the number of cycles we are waiting for DECODE stage to accept 112 | p_dc_stall_delay : process (clk_i) 113 | begin 114 | if rising_edge(clk_i) then 115 | if dc_valid_o and not dc_ready_i then 116 | f_dc_stall_delay <= f_dc_stall_delay + 1; 117 | else 118 | f_dc_stall_delay <= 0; 119 | end if; 120 | end if; 121 | end process p_dc_stall_delay; 122 | 123 | -- Record the last valid address sent to the DECODE stage 124 | p_dc_last_addr : process (clk_i) 125 | begin 126 | if rising_edge(clk_i) then 127 | if dc_valid_o = '1' then 128 | f_dc_last_addr_valid <= '1'; 129 | f_dc_last_addr <= dc_addr_o; 130 | end if; 131 | 132 | if rst_i = '1' or dc_valid_i = '1' then 133 | f_dc_last_addr_valid <= '0'; 134 | end if; 135 | end if; 136 | end process p_dc_last_addr; 137 | 138 | -- Verify that the DECODE output is stable while not received, unless aborted. 139 | f_dc_stable : assert always {dc_valid_o and not dc_ready_i and not rst_i and not dc_valid_i} |=> {stable(dc_valid_o) and stable(dc_addr_o) and stable(dc_data_o)}; 140 | 141 | -- Artifically constrain the maximum amount of time the DECODE may stall 142 | f_dc_stall_delay_max : assume always {f_dc_stall_delay <= 2}; 143 | 144 | -- Validate that the address forwarded to the DECODE stage continuously 145 | -- increments by one. 146 | f_dc_addr : assert always {dc_valid_o and dc_ready_i; f_dc_last_addr_valid and dc_valid_o} |-> {dc_addr_o = f_dc_last_addr + 1}; 147 | 148 | 149 | ---------------------------- 150 | -- VERIFYING THE DATA PATH 151 | ---------------------------- 152 | 153 | -- We want to make sure that the DECODE stage receives the correct data. 154 | -- We do this by artifically constraining the data received on the WISHBONE 155 | -- interface. 156 | f_wb_data : assume always {wb_cyc_o and wb_ack_i} |-> wb_data_i = not wb_addr_o; 157 | 158 | -- Verify data sent to DECODE satisfies the same artifical constraint as 159 | -- the WISHBONE interface. 160 | f_dc_data : assert always {dc_valid_o} |-> dc_data_o = not dc_addr_o; 161 | 162 | 163 | ----------------------------- 164 | -- ASSUMPTIONS ABOUT INPUTS 165 | ----------------------------- 166 | 167 | -- Require reset at startup. 168 | -- This is to ensure BMC starts in a valid state. 169 | f_reset : assume {rst_i}; 170 | 171 | -- Assume DECODE starts by sending a new PC right after reset. 172 | -- This is to ensure BMC starts in a valid state. 173 | f_dc_after_reset : assume always {rst_i} |=> dc_valid_i; 174 | 175 | 176 | -------------------------------------------- 177 | -- INTERNAL ASSERTIONS 178 | -------------------------------------------- 179 | 180 | f_osb_stable : assert always {osb_in_valid and not osb_in_ready and not rst_i and not dc_valid_i} |=> {stable(osb_in_valid) and stable(osb_in_data)}; 181 | 182 | 183 | -------------------------------------------- 184 | -- COVER STATEMENTS TO VERIFY REACHABILITY 185 | -------------------------------------------- 186 | 187 | -- DECODE stage accepts data 188 | f_dc_accept : cover {dc_valid_o; not dc_valid_o}; 189 | 190 | -- DECODE stage receives two data cycles back-to-back 191 | f_dc_back2back : cover {dc_valid_o and dc_ready_i; dc_valid_o}; 192 | 193 | } -- vunit i_fetch(fetch(synthesis)) 194 | 195 | -------------------------------------------------------------------------------- /fetch/fetch.sby: -------------------------------------------------------------------------------- 1 | [tasks] 2 | cover 3 | prove 4 | 5 | [options] 6 | cover: mode cover 7 | cover: depth 10 8 | prove: mode prove 9 | prove: depth 12 10 | 11 | [engines] 12 | smtbmc 13 | 14 | [script] 15 | ghdl --std=08 fetch.vhd one_stage_buffer.vhd fetch.psl -e fetch 16 | prep -top fetch 17 | 18 | [files] 19 | fetch.vhd 20 | fetch.psl 21 | ../one_stage_buffer/one_stage_buffer.vhd 22 | 23 | -------------------------------------------------------------------------------- /fetch/fetch.vhd: -------------------------------------------------------------------------------- 1 | library ieee; 2 | use ieee.std_logic_1164.all; 3 | use ieee.numeric_std_unsigned.all; 4 | 5 | -- A simple instruction fetch unit. 6 | -- This unit has four interfaces: 7 | -- 1. Sending read requests to WISHBONE (with possible backpressure) 8 | -- 2. Receiving read responses from WISHBONE 9 | -- 3. Sending instructions to DECODE stage (with possible backpressure) 10 | -- 4. Receiving a new PC from DECODE 11 | 12 | -- The wishbone interface is running in pipeline mode. This means the STB 13 | -- signal is asserted for one clock cycle (or until STALL is low) for each 14 | -- request, whereas the CYC signal is held high until the corresponding ACKs 15 | -- are received. In this implementation, only a single outstanding wishbone 16 | -- request is used. 17 | 18 | entity fetch is 19 | port ( 20 | clk_i : in std_logic; 21 | rst_i : in std_logic; 22 | 23 | -- Send read request to WISHBONE 24 | wb_cyc_o : out std_logic; 25 | wb_stb_o : out std_logic; 26 | wb_stall_i : in std_logic; 27 | wb_addr_o : out std_logic_vector(15 downto 0); 28 | 29 | -- Receive read response from WISHBONE 30 | wb_ack_i : in std_logic; 31 | wb_data_i : in std_logic_vector(15 downto 0); 32 | 33 | -- Send instruction to DECODE 34 | dc_valid_o : out std_logic; 35 | dc_ready_i : in std_logic; 36 | dc_addr_o : out std_logic_vector(15 downto 0); 37 | dc_data_o : out std_logic_vector(15 downto 0); 38 | 39 | -- Receive a new PC from DECODE 40 | dc_valid_i : in std_logic; 41 | dc_addr_i : in std_logic_vector(15 downto 0) 42 | ); 43 | end entity fetch; 44 | 45 | architecture synthesis of fetch is 46 | 47 | -- Registered output signals 48 | signal wb_cyc : std_logic := '0'; 49 | signal wb_stb : std_logic := '0'; 50 | signal wb_addr : std_logic_vector(15 downto 0); 51 | signal dc_valid : std_logic := '0'; 52 | signal dc_addr : std_logic_vector(15 downto 0); 53 | signal dc_data : std_logic_vector(15 downto 0); 54 | 55 | -- Combinatorial signals 56 | signal wb_wait_for_ack : std_logic; 57 | 58 | -- Connected to one_stage_buffer 59 | signal osb_rst : std_logic; 60 | signal osb_in_valid : std_logic; 61 | signal osb_in_ready : std_logic; 62 | signal osb_in_data : std_logic_vector(31 downto 0); 63 | signal osb_out_valid : std_logic; 64 | signal osb_out_ready : std_logic; 65 | signal osb_out_data : std_logic_vector(31 downto 0); 66 | 67 | subtype R_OSB_ADDR is natural range 31 downto 16; 68 | subtype R_OSB_DATA is natural range 15 downto 0; 69 | 70 | begin 71 | 72 | wb_wait_for_ack <= wb_cyc and not wb_ack_i; 73 | 74 | -- Control the wishbone request interface 75 | p_wishbone : process (clk_i) 76 | begin 77 | if rising_edge(clk_i) then 78 | -- Clear request when it has been accepted 79 | if wb_stall_i = '0' then 80 | wb_stb <= '0'; 81 | end if; 82 | 83 | -- End cycle when response received 84 | if wb_cyc = '1' and wb_ack_i = '1' then 85 | wb_cyc <= '0'; 86 | wb_stb <= '0'; 87 | end if; 88 | 89 | -- Increment address when response received 90 | if wb_cyc = '1' and wb_ack_i = '1' then 91 | wb_addr <= wb_addr + 1; 92 | end if; 93 | 94 | -- Start new transaction when response received and ready to issue new request 95 | if wb_wait_for_ack = '0' and (osb_out_valid = '0' or (osb_in_valid = '0' and osb_out_ready = '1')) then 96 | wb_cyc <= '1'; 97 | wb_stb <= '1'; 98 | end if; 99 | 100 | -- Abort current wishbone transaction 101 | if dc_valid_i = '1' then 102 | wb_addr <= dc_addr_i; 103 | wb_cyc <= '0'; 104 | wb_stb <= '0'; 105 | end if; 106 | 107 | -- If no current transaction start new one immediately 108 | if dc_valid_i = '1' and wb_cyc = '0' then 109 | wb_cyc <= '1'; 110 | wb_stb <= '1'; 111 | end if; 112 | 113 | if rst_i = '1' then 114 | wb_cyc <= '0'; 115 | wb_stb <= '0'; 116 | end if; 117 | end if; 118 | end process p_wishbone; 119 | 120 | 121 | -- Control the decode output signals 122 | p_decode : process (clk_i) 123 | begin 124 | if rising_edge(clk_i) then 125 | -- Clear output when it has been accepted 126 | if osb_in_ready = '1' or dc_valid_i = '1' then 127 | osb_in_valid <= '0'; 128 | end if; 129 | 130 | -- Output data received from wishbone 131 | if wb_cyc = '1' and wb_ack_i = '1' and dc_valid_i = '0' then 132 | osb_in_data(R_OSB_ADDR) <= wb_addr; 133 | osb_in_data(R_OSB_DATA) <= wb_data_i; 134 | osb_in_valid <= '1'; 135 | end if; 136 | 137 | if rst_i = '1' then 138 | osb_in_valid <= '0'; 139 | end if; 140 | end if; 141 | end process p_decode; 142 | 143 | osb_rst <= rst_i or dc_valid_i; 144 | 145 | i_one_stage_buffer : entity work.one_stage_buffer 146 | generic map ( 147 | G_DATA_SIZE => 32 148 | ) 149 | port map ( 150 | clk_i => clk_i, 151 | rst_i => osb_rst, 152 | s_valid_i => osb_in_valid, 153 | s_ready_o => osb_in_ready, 154 | s_data_i => osb_in_data, 155 | m_valid_o => osb_out_valid, 156 | m_ready_i => osb_out_ready, 157 | m_data_o => osb_out_data 158 | ); -- i_one_stage_buffer 159 | 160 | dc_addr <= osb_out_data(R_OSB_ADDR); 161 | dc_data <= osb_out_data(R_OSB_DATA); 162 | dc_valid <= osb_out_valid; 163 | osb_out_ready <= dc_ready_i; 164 | 165 | 166 | -- Connect output signals 167 | wb_cyc_o <= wb_cyc; 168 | wb_stb_o <= wb_stb; 169 | wb_addr_o <= wb_addr; 170 | dc_valid_o <= dc_valid; 171 | dc_addr_o <= dc_addr; 172 | dc_data_o <= dc_data; 173 | 174 | end architecture synthesis; 175 | 176 | -------------------------------------------------------------------------------- /fetch/waveform.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MJoergen/formal/9e0bc6c20361925199b3a94eb3dd362aba29b010/fetch/waveform.png -------------------------------------------------------------------------------- /fetch2/Makefile: -------------------------------------------------------------------------------- 1 | # Type 'make formal' to run formal verification 2 | # Type 'make synth' to run synthesis 3 | 4 | DUT = fetch 5 | SRC += ../pipe_concat/pipe_concat.vhd 6 | SRC += ../one_stage_buffer/one_stage_buffer.vhd 7 | SRC += ../two_stage_buffer/two_stage_buffer.vhd 8 | SRC += ../two_stage_fifo/two_stage_fifo.vhd 9 | SRC += $(DUT).vhd 10 | 11 | 12 | ####################### 13 | # Formal verification 14 | ####################### 15 | 16 | .PHONY: formal 17 | formal: $(DUT)_cover/PASS $(DUT)_prove/PASS 18 | $(DUT)_cover/PASS: $(DUT).sby $(DUT).psl $(SRC) 19 | # This is the main command line to run the formal verification 20 | sby --yosys "yosys -m ghdl" -f $(DUT).sby 21 | 22 | show_prove: 23 | gtkwave $(DUT)_prove/engine_0/trace_induct.vcd $(DUT).gtkw 24 | 25 | 26 | ####################### 27 | # Synthesis 28 | ####################### 29 | 30 | .PHONY: synth 31 | synth: work-obj08.cf 32 | yosys -m ghdl -p 'ghdl -fpsl -fsynopsys --std=08 $(DUT); synth_xilinx -top $(DUT) -edif $(DUT).edif' > yosys.log 33 | 34 | work-obj08.cf: $(SRC) 35 | ghdl -a -fpsl -fsynopsys --std=08 $^ 36 | 37 | 38 | ####################### 39 | # Cleanup 40 | ####################### 41 | 42 | .PHONY: clean 43 | clean: 44 | rm -rf $(DUT)_cover/ 45 | rm -rf $(DUT)_prove/ 46 | rm -rf work-obj08.cf 47 | rm -rf yosys.log 48 | rm -rf $(DUT).edif 49 | 50 | -------------------------------------------------------------------------------- /fetch2/README.md: -------------------------------------------------------------------------------- 1 | # FETCH module of a yet-to-be-made CPU (optimized version) 2 | 3 | The [previous version](../fetch) of the FETCH module was not completely 4 | optimized, and in particular could not sustain a 100% throughput even in best 5 | case. In this optimized version of the FETCH module I will rewrite the module 6 | from scratch to get better performance. 7 | 8 | ## Implementation 9 | 10 | The strategy this time is much different. From the previous version I was 11 | quickly overwhelmed by complexity, even for such a simple module. So this time 12 | I will use a more hierarchical and general approach. 13 | 14 | The idea is to consider the WISHBONE interface as two independent data streams: 15 | A sequence of addresses going out, and a corresponding sequence of data coming 16 | in. And pairs of (address,data) is to be sent to the DECODE stage. 17 | 18 | Therefore I instantiate two FIFOs. The first FIFO is filled with the stream 19 | of addresses sent to the WISHBONE interface, and the second FIFO is filled with 20 | the streams of data received from the WISHBONE interface. Finally, the outputs 21 | of these two FIFOs are concatenated and sent to the DECODE stage. 22 | 23 | In this particular version I expect the WISHBONE data to arrive on the next 24 | clock cycle, i.e. a latency of just one clock cycle. Therefore, at most two 25 | requests need to be stored in the address FIFO, and we can therefore use the 26 | pre-existing [two_stage_fifo](../two_stage_fifo). 27 | 28 | The data FIFO must also be able to store two words, and to reduce latency we 29 | can conveniently use a [two_stage_buffer](../two_stage_buffer). So the bulk of 30 | the implementation is shown in the skeleton code below: 31 | 32 | ``` 33 | i_two_stage_fifo_addr : entity work.two_stage_fifo 34 | port map ( 35 | s_valid_i => wb_cyc_o and wb_stb_o and not wb_stall_i, 36 | s_ready_o => tsf_in_addr_ready, 37 | s_data_i => wb_addr_o, 38 | m_data_o => tsf_out_addr_data 39 | etc... 40 | 41 | i_two_stage_buffer_data : entity work.two_stage_buffer 42 | port map ( 43 | s_valid_i => wb_cyc_o and wb_ack_i, 44 | s_ready_o => tsb_in_data_ready, 45 | s_data_i => wb_data_i, 46 | m_data_o => tsb_out_data_data 47 | etc... 48 | 49 | i_pipe_concat : entity work.pipe_concat 50 | port map ( 51 | s1_data_i => tsf_out_addr_data, 52 | s0_data_i => tsb_out_data_data, 53 | m_valid_o => dc_valid_o, 54 | m_ready_i => dc_ready_i, 55 | m_data_o(31 downto 16) => dc_addr_o, 56 | m_data_o(15 downto 0) => dc_data_o 57 | etc... 58 | ``` 59 | 60 | With this approach I'm guaranteed that the address and data signals will always 61 | be in sync. The only remaining code is controlling the WISHBONE requests, and 62 | it's all done in a single process: 63 | 64 | As usual, the first thing is to clear the WISHBONE request whenever it has been 65 | accepted: 66 | ``` 67 | if not wb_stall_i then 68 | wb_stb_o <= '0'; 69 | end if; 70 | ``` 71 | 72 | We want the wishbone address to increment for each request. The time to 73 | increment it is right after the request has been accepted: 74 | ``` 75 | if wb_cyc_o and wb_stb_o and not wb_stall_i then 76 | wb_addr_o <= wb_addr_o + 1; 77 | end if; 78 | ``` 79 | 80 | If there are no active requests, we should release the bus. The lines below 81 | will release the bus one clock later. So there is a potential optimizatin here, 82 | since the bus could be released on the **same** clock cycle as 83 | `tsf_in_addr_fill` being zero, i.e one clock cycle earlier than what we're 84 | doing below. 85 | ``` 86 | if tsf_in_addr_fill = "00" then 87 | wb_cyc_o <= '0'; 88 | end if; 89 | ``` 90 | 91 | When a new address is received from the DECODE stage we should store it, and abort 92 | any current wishbone transactions. 93 | ``` 94 | if dc_valid_i = '1' then 95 | wb_addr_o <= dc_addr_i; 96 | wb_cyc_o <= '0'; 97 | wb_stb_o <= '0'; 98 | end if; 99 | ``` 100 | 101 | The tricky part is when to start a new transaction. We have to make sure that 102 | when the address is requested (on the next clock cycle) we have room in the 103 | address FIFO. Furthermore we must make sure that there is room in the data FIFO 104 | for the wishbone result. And finally, if a new address is received from the 105 | DECODE stage, we should abort the current transaction first, i.e. let the 106 | CYC line drop low for a clock cycle before going high again. All this is 107 | encoded as follows: 108 | ``` 109 | if (tsf_out_addr_ready or nor(tsf_in_addr_fill)) and tsb_in_data_ready and not (wb_cyc_o and dc_valid_i) then 110 | wb_cyc_o <= '1'; 111 | wb_stb_o <= '1'; 112 | end if; 113 | ``` 114 | 115 | Finally, we clear everything upon reset. 116 | ``` 117 | if rst_i = '1' then 118 | wb_cyc_o <= '0'; 119 | wb_stb_o <= '0'; 120 | end if; 121 | ``` 122 | 123 | ## Formal verification 124 | In the implementation above notice that the data received from the WISHBONE 125 | interface has no back-pressure. Therefore, we must ASSERT that the 126 | `two_stage_buffer` always will accept the incoming data. This is handled by the 127 | following assertion. 128 | 129 | ``` 130 | f_data_ready : assert always {wb_cyc_o and wb_ack_i} |-> {tsb_in_data_ready}; 131 | ``` 132 | 133 | The same property must hold for the address entering the address fifo since 134 | that too has no back pressure. So we have the corresponding ASSERT on the 135 | address fifo input: 136 | 137 | ``` 138 | f_addr_ready : assert always {wb_cyc_o and wb_stb_o and not wb_stall_i} |-> {tsf_in_addr_ready}; 139 | ``` 140 | 141 | Note that these two assertions make use of the internal signals 142 | `tsb_in_data_ready` and `tsf_in_addr_ready`. 143 | 144 | ## Running formal verification 145 | ![Waveform](waveform.png) 146 | 147 | ## Synthesis 148 | ``` 149 | Number of cells: 271 150 | BUFG 1 151 | CARRY4 4 152 | FDRE 85 153 | FDSE 1 154 | IBUF 38 155 | INV 1 156 | LUT2 11 157 | LUT3 55 158 | LUT4 21 159 | LUT5 1 160 | LUT6 2 161 | OBUF 51 162 | 163 | Estimated number of LCs: 79 164 | ``` 165 | 166 | -------------------------------------------------------------------------------- /fetch2/fetch.gtkw: -------------------------------------------------------------------------------- 1 | [*] 2 | [*] GTKWave Analyzer v3.3.103 (w)1999-2019 BSI 3 | [*] Wed Jan 6 20:45:51 2021 4 | [*] 5 | [dumpfile] "/home/mike/git/MJoergen/formal/fetch2/fetch_bmc/engine_0/trace.vcd" 6 | [dumpfile_mtime] "Wed Jan 6 20:44:58 2021" 7 | [dumpfile_size] 9925 8 | [savefile] "/home/mike/git/MJoergen/formal/fetch2/fetch.gtkw" 9 | [timestart] 0 10 | [size] 1885 1091 11 | [pos] 18 0 12 | *-4.633660 104 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 13 | [treeopen] fetch. 14 | [sst_width] 313 15 | [signals_width] 338 16 | [sst_expanded] 1 17 | [sst_vpaned_height] 313 18 | @28 19 | fetch.clk_i 20 | fetch.rst_i 21 | @200 22 | - 23 | @28 24 | [color] 2 25 | fetch.dc_valid_i 26 | @22 27 | fetch.dc_addr_i[15:0] 28 | @200 29 | - 30 | @28 31 | [color] 2 32 | fetch.wb_cyc_o 33 | [color] 2 34 | fetch.wb_stb_o 35 | [color] 7 36 | fetch.wb_stall_i 37 | @22 38 | fetch.wb_addr_o[15:0] 39 | @200 40 | - 41 | @28 42 | [color] 2 43 | fetch.wb_ack_i 44 | @22 45 | fetch.wb_data_i[15:0] 46 | @200 47 | - 48 | @28 49 | [color] 2 50 | fetch.dc_valid_o 51 | [color] 7 52 | fetch.dc_ready_i 53 | @22 54 | fetch.dc_addr_o[15:0] 55 | fetch.dc_data_o[15:0] 56 | @200 57 | - 58 | @c00200 59 | -formal 60 | @24 61 | fetch.i_fetch.f_tsf_addr_size[1:0] 62 | fetch.i_fetch.f_wb_req_count[1:0] 63 | fetch.i_fetch.f_wb_stall_delay[1:0] 64 | @22 65 | fetch.i_fetch.f_wb_addr[15:0] 66 | @24 67 | fetch.i_fetch.f_wb_ack_delay[1:0] 68 | fetch.i_fetch.f_dc_stall_delay[1:0] 69 | @28 70 | fetch.i_fetch.f_dc_last_addr_valid 71 | @22 72 | fetch.i_fetch.f_dc_last_addr[15:0] 73 | @1401200 74 | -formal 75 | @200 76 | - 77 | @800200 78 | -tsf_addr 79 | @28 80 | [color] 2 81 | fetch.i_two_stage_fifo_addr.s_valid_i 82 | [color] 7 83 | fetch.i_two_stage_fifo_addr.s_ready_o 84 | @22 85 | fetch.i_two_stage_fifo_addr.s_data_i[15:0] 86 | @28 87 | fetch.i_two_stage_fifo_addr.s_fill_o[1:0] 88 | [color] 2 89 | fetch.i_two_stage_fifo_addr.m_valid_o 90 | [color] 7 91 | fetch.i_two_stage_fifo_addr.m_ready_i 92 | @22 93 | fetch.i_two_stage_fifo_addr.m_data_o[15:0] 94 | @1000200 95 | -tsf_addr 96 | @200 97 | - 98 | @800200 99 | -tsb_data 100 | @28 101 | [color] 2 102 | fetch.i_two_stage_buffer_data.s_valid_i 103 | [color] 7 104 | fetch.i_two_stage_buffer_data.s_ready_o 105 | @22 106 | fetch.i_two_stage_buffer_data.s_data_i[15:0] 107 | @28 108 | fetch.i_two_stage_buffer_data.s_fill_o[1:0] 109 | [color] 2 110 | fetch.i_two_stage_buffer_data.m_valid_o 111 | [color] 7 112 | fetch.i_two_stage_buffer_data.m_ready_i 113 | @22 114 | fetch.i_two_stage_buffer_data.m_data_o[15:0] 115 | @1000200 116 | -tsb_data 117 | [pattern_trace] 1 118 | [pattern_trace] 0 119 | -------------------------------------------------------------------------------- /fetch2/fetch.sby: -------------------------------------------------------------------------------- 1 | [tasks] 2 | cover 3 | prove 4 | 5 | [options] 6 | cover: mode cover 7 | cover: depth 10 8 | cover: append 3 9 | prove: mode prove 10 | prove: depth 10 11 | 12 | [engines] 13 | smtbmc 14 | 15 | [script] 16 | ghdl --std=08 fetch.vhd fetch.psl \ 17 | one_stage_buffer.vhd one_stage_buffer.psl \ 18 | two_stage_buffer.vhd two_stage_buffer.psl \ 19 | two_stage_fifo.vhd two_stage_fifo.psl \ 20 | pipe_concat.vhd pipe_concat.psl \ 21 | -e fetch 22 | prep -top fetch 23 | chformal -assume2assert fetch/* %M 24 | 25 | [files] 26 | fetch.vhd 27 | fetch.psl 28 | ../pipe_concat/pipe_concat.vhd 29 | ../pipe_concat/pipe_concat.psl 30 | ../one_stage_buffer/one_stage_buffer.vhd 31 | ../one_stage_buffer/one_stage_buffer.psl 32 | ../two_stage_buffer/two_stage_buffer.vhd 33 | ../two_stage_buffer/two_stage_buffer.psl 34 | ../two_stage_fifo/two_stage_fifo.vhd 35 | ../two_stage_fifo/two_stage_fifo.psl 36 | 37 | -------------------------------------------------------------------------------- /fetch2/fetch.vhd: -------------------------------------------------------------------------------- 1 | library ieee; 2 | use ieee.std_logic_1164.all; 3 | use ieee.numeric_std_unsigned.all; 4 | 5 | -- A simple instruction fetch unit. 6 | -- This unit has four interfaces: 7 | -- 1. Sending read requests to WISHBONE (with possible backpressure) 8 | -- 2. Receiving read responses from WISHBONE 9 | -- 3. Sending instructions to DECODE stage (with possible backpressure) 10 | -- 4. Receiving a new PC from DECODE 11 | 12 | -- The wishbone interface is running in pipeline mode. This means the STB 13 | -- signal is asserted for one clock cycle (or until STALL is low) for each 14 | -- request, whereas the CYC signal is held high until the corresponding ACKs 15 | -- are received. In this implementation, up to two outstanding wishbone 16 | -- request are used. 17 | 18 | entity fetch is 19 | port ( 20 | clk_i : in std_logic; 21 | rst_i : in std_logic; 22 | 23 | -- Send read request to WISHBONE 24 | wb_cyc_o : out std_logic := '0'; 25 | wb_stb_o : out std_logic := '0'; 26 | wb_stall_i : in std_logic; 27 | wb_addr_o : out std_logic_vector(15 downto 0); 28 | 29 | -- Receive read response from WISHBONE 30 | wb_ack_i : in std_logic; 31 | wb_data_i : in std_logic_vector(15 downto 0); 32 | 33 | -- Send instruction to DECODE 34 | dc_valid_o : out std_logic := '0'; 35 | dc_ready_i : in std_logic; 36 | dc_addr_o : out std_logic_vector(15 downto 0); 37 | dc_data_o : out std_logic_vector(15 downto 0); 38 | 39 | -- Receive a new PC from DECODE 40 | dc_valid_i : in std_logic; 41 | dc_addr_i : in std_logic_vector(15 downto 0) 42 | ); 43 | end entity fetch; 44 | 45 | architecture synthesis of fetch is 46 | 47 | signal tsf_in_addr_ready : std_logic; 48 | signal tsf_in_addr_fill : std_logic_vector(1 downto 0); 49 | signal tsf_out_addr_valid : std_logic; 50 | signal tsf_out_addr_ready : std_logic; 51 | signal tsf_out_addr_data : std_logic_vector(15 downto 0); 52 | 53 | signal tsb_in_data_ready : std_logic; 54 | signal tsb_in_data_fill : std_logic_vector(1 downto 0); 55 | signal tsb_out_data_valid : std_logic; 56 | signal tsb_out_data_ready : std_logic; 57 | signal tsb_out_data_data : std_logic_vector(15 downto 0); 58 | 59 | begin 60 | 61 | -- Control the wishbone request interface 62 | p_wishbone : process (clk_i) 63 | begin 64 | if rising_edge(clk_i) then 65 | -- Clear request when it has been accepted 66 | if not wb_stall_i then 67 | wb_stb_o <= '0'; 68 | end if; 69 | 70 | -- Increment address when request has been accepted 71 | if wb_cyc_o and wb_stb_o and not wb_stall_i then 72 | wb_addr_o <= wb_addr_o + 1; 73 | end if; 74 | 75 | -- Clear transaction when no requests active 76 | if tsf_in_addr_fill = "00" then 77 | wb_cyc_o <= '0'; 78 | end if; 79 | 80 | -- Abort current wishbone transaction 81 | if dc_valid_i = '1' then 82 | wb_addr_o <= dc_addr_i; 83 | wb_cyc_o <= '0'; 84 | wb_stb_o <= '0'; 85 | end if; 86 | 87 | -- Start new transaction when ready to receive response 88 | if (tsf_out_addr_ready or nor(tsf_in_addr_fill)) and tsb_in_data_ready and not (wb_cyc_o and dc_valid_i) then 89 | wb_cyc_o <= '1'; 90 | wb_stb_o <= '1'; 91 | end if; 92 | 93 | if rst_i = '1' then 94 | wb_cyc_o <= '0'; 95 | wb_stb_o <= '0'; 96 | end if; 97 | end if; 98 | end process p_wishbone; 99 | 100 | 101 | -- FIFO to store the WISHBONE address 102 | i_two_stage_fifo_addr : entity work.two_stage_fifo 103 | generic map ( 104 | G_DATA_SIZE => 16 105 | ) 106 | port map ( 107 | clk_i => clk_i, 108 | rst_i => rst_i or dc_valid_i, 109 | s_valid_i => wb_cyc_o and wb_stb_o and not wb_stall_i, 110 | s_ready_o => tsf_in_addr_ready, 111 | s_data_i => wb_addr_o, 112 | s_fill_o => tsf_in_addr_fill, 113 | m_valid_o => tsf_out_addr_valid, 114 | m_ready_i => tsf_out_addr_ready, 115 | m_data_o => tsf_out_addr_data 116 | ); -- i_two_stage_fifo_addr 117 | 118 | 119 | -- FIFO to store the WISHBONE data 120 | i_two_stage_buffer_data : entity work.two_stage_buffer 121 | generic map ( 122 | G_DATA_SIZE => 16 123 | ) 124 | port map ( 125 | clk_i => clk_i, 126 | rst_i => rst_i or dc_valid_i, 127 | s_valid_i => wb_cyc_o and wb_ack_i, 128 | s_ready_o => tsb_in_data_ready, 129 | s_data_i => wb_data_i, 130 | s_fill_o => tsb_in_data_fill, 131 | m_valid_o => tsb_out_data_valid, 132 | m_ready_i => tsb_out_data_ready, 133 | m_data_o => tsb_out_data_data 134 | ); -- i_two_stage_buffer_data 135 | 136 | 137 | -- Concatenate WISHBONE address and data 138 | i_pipe_concat : entity work.pipe_concat 139 | generic map ( 140 | G_DATA0_SIZE => 16, 141 | G_DATA1_SIZE => 16 142 | ) 143 | port map ( 144 | clk_i => clk_i, 145 | rst_i => rst_i or dc_valid_i, 146 | s1_valid_i => tsf_out_addr_valid, 147 | s1_ready_o => tsf_out_addr_ready, 148 | s1_data_i => tsf_out_addr_data, 149 | s0_valid_i => tsb_out_data_valid, 150 | s0_ready_o => tsb_out_data_ready, 151 | s0_data_i => tsb_out_data_data, 152 | m_valid_o => dc_valid_o, 153 | m_ready_i => dc_ready_i, 154 | m_data_o(31 downto 16) => dc_addr_o, 155 | m_data_o(15 downto 0) => dc_data_o 156 | ); -- i_pipe_concat 157 | 158 | end architecture synthesis; 159 | 160 | -------------------------------------------------------------------------------- /fetch2/waveform.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MJoergen/formal/9e0bc6c20361925199b3a94eb3dd362aba29b010/fetch2/waveform.png -------------------------------------------------------------------------------- /fgc/Makefile: -------------------------------------------------------------------------------- 1 | DUT = fgc 2 | 3 | # This is the main command line to run the formal verification 4 | all: 5 | sby --yosys "yosys -m ghdl" -f $(DUT).sby 6 | 7 | show: 8 | gtkwave $(DUT)/engine_0/trace0.vcd $(DUT).gtkw 9 | 10 | clean: 11 | rm -rf $(DUT)/ 12 | 13 | -------------------------------------------------------------------------------- /fgc/README.md: -------------------------------------------------------------------------------- 1 | # Fox, Goat, and Cabbage 2 | This is a small example idea I copied from [this 3 | video](https://www.youtube.com/watch?v=H3tsP9tjYdY). 4 | 5 | To run this example just type 6 | 7 | ``` 8 | make 9 | ``` 10 | 11 | To show the solution, type 12 | 13 | ``` 14 | make show 15 | ``` 16 | 17 | ![Waveform](waveform.png) 18 | 19 | 20 | To clean up, type 21 | 22 | ``` 23 | make clean 24 | ``` 25 | 26 | -------------------------------------------------------------------------------- /fgc/fgc.gtkw: -------------------------------------------------------------------------------- 1 | [*] 2 | [*] GTKWave Analyzer v3.3.86 (w)1999-2017 BSI 3 | [*] Sun Dec 13 10:42:51 2020 4 | [*] 5 | [dumpfile] "/home/mike/git/MJoergen/formal/fgc/fgc/engine_0/trace0.vcd" 6 | [dumpfile_mtime] "Sun Dec 13 10:42:10 2020" 7 | [dumpfile_size] 2669 8 | [savefile] "/home/mike/git/MJoergen/formal/fgc/fgc.gtkw" 9 | [timestart] 0 10 | [size] 1920 1011 11 | [pos] -1 -1 12 | *-3.871615 86 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 13 | [treeopen] fgc_formal. 14 | [sst_width] 249 15 | [signals_width] 244 16 | [sst_expanded] 1 17 | [sst_vpaned_height] 286 18 | @28 19 | fgc.clk_i 20 | fgc.rst_i 21 | @800029 22 | fgc.item_i[1:0] 23 | @29 24 | (0)fgc.item_i[1:0] 25 | (1)fgc.item_i[1:0] 26 | @1001201 27 | -group_end 28 | @28 29 | fgc.bank_c_o 30 | fgc.bank_f_o 31 | fgc.bank_g_o 32 | fgc.bank_m_o 33 | [pattern_trace] 1 34 | [pattern_trace] 0 35 | -------------------------------------------------------------------------------- /fgc/fgc.psl: -------------------------------------------------------------------------------- 1 | vunit i_fgc(fgc(synthesis)) 2 | { 3 | -- set all declarations to run on clk_i 4 | default clock is rising_edge(clk_i); 5 | 6 | 7 | ----------------------------- 8 | -- ASSUMPTIONS ABOUT INPUTS 9 | ----------------------------- 10 | 11 | -- Require reset at startup. 12 | f_reset : assume {rst_i}; 13 | 14 | -- Fox and Goat can not be alone 15 | f_fox_goat : assume always {bank_f_o = bank_g_o} |-> bank_m_o = bank_f_o; 16 | 17 | -- Goat and cabbage can not be alone 18 | f_goat_cabbage : assume always {bank_g_o = bank_c_o} |-> bank_m_o = bank_c_o; 19 | 20 | 21 | -------------------------------------------- 22 | -- COVER STATEMENTS TO VERIFY REACHABILITY 23 | -------------------------------------------- 24 | 25 | -- Attempt to have everything on bank 1 26 | cover {bank_m_o and bank_f_o and bank_g_o and bank_c_o}; 27 | 28 | } -- vunit i_fgc(fgc(synthesis)) 29 | 30 | -------------------------------------------------------------------------------- /fgc/fgc.sby: -------------------------------------------------------------------------------- 1 | [options] 2 | mode cover 3 | depth 20 4 | 5 | [engines] 6 | smtbmc 7 | 8 | [script] 9 | ghdl --std=08 fgc.vhd fgc.psl -e fgc 10 | prep -top fgc 11 | 12 | [files] 13 | fgc.vhd 14 | fgc.psl 15 | 16 | -------------------------------------------------------------------------------- /fgc/fgc.vhd: -------------------------------------------------------------------------------- 1 | library ieee; 2 | use ieee.std_logic_1164.all; 3 | use ieee.numeric_std.all; 4 | 5 | -- A simple demonstration of using formal verification 6 | -- to solve the Fox-Goat-Cabbage problem 7 | 8 | entity fgc is 9 | port ( 10 | clk_i : in std_logic; 11 | rst_i : in std_logic; 12 | 13 | -- THe boat can only carry one of the three items 14 | item_i : in std_logic_vector(1 downto 0); 15 | 16 | -- Current status 17 | bank_f_o : out std_logic; 18 | bank_g_o : out std_logic; 19 | bank_c_o : out std_logic; 20 | bank_m_o : out std_logic 21 | ); 22 | end entity fgc; 23 | 24 | architecture synthesis of fgc is 25 | 26 | signal bank_f : std_logic := '0'; 27 | signal bank_g : std_logic := '0'; 28 | signal bank_c : std_logic := '0'; 29 | signal bank_m : std_logic := '0'; 30 | 31 | begin 32 | 33 | p_move : process (clk_i) 34 | begin 35 | if rising_edge(clk_i) then 36 | case item_i is 37 | when "00" => 38 | -- Move MAN only 39 | bank_m <= not bank_m; 40 | 41 | when "01" => 42 | if bank_f = bank_m then 43 | -- Move MAN and FOX 44 | bank_f <= not bank_f; 45 | bank_m <= not bank_m; 46 | end if; 47 | 48 | when "10" => 49 | if bank_g = bank_m then 50 | -- Move MAN and GOAT 51 | bank_g <= not bank_g; 52 | bank_m <= not bank_m; 53 | end if; 54 | 55 | when "11" => 56 | if bank_c = bank_m then 57 | -- Move MAN and CABBAGE 58 | bank_c <= not bank_c; 59 | bank_m <= not bank_m; 60 | end if; 61 | 62 | when others => 63 | null; 64 | end case; 65 | 66 | if rst_i = '1' then 67 | -- Initially, everything is on bank 0 68 | bank_f <= '0'; 69 | bank_g <= '0'; 70 | bank_c <= '0'; 71 | bank_m <= '0'; 72 | end if; 73 | end if; 74 | end process p_move; 75 | 76 | -- Connect output signals 77 | bank_f_o <= bank_f; 78 | bank_g_o <= bank_g; 79 | bank_c_o <= bank_c; 80 | bank_m_o <= bank_m; 81 | 82 | end architecture synthesis; 83 | 84 | -------------------------------------------------------------------------------- /fgc/waveform.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MJoergen/formal/9e0bc6c20361925199b3a94eb3dd362aba29b010/fgc/waveform.png -------------------------------------------------------------------------------- /memory/Makefile: -------------------------------------------------------------------------------- 1 | DUT = memory 2 | 3 | # This is the main command line to run the formal verification 4 | all: 5 | sby --yosys "yosys -m ghdl" -f $(DUT).sby 6 | 7 | show_bmc: 8 | gtkwave $(DUT)_bmc/engine_0/trace.vcd $(DUT).gtkw 9 | 10 | show_cover2: 11 | gtkwave $(DUT)_cover/engine_0/trace2.vcd $(DUT).gtkw 12 | 13 | show_cover3: 14 | gtkwave $(DUT)_cover/engine_0/trace3.vcd $(DUT).gtkw 15 | 16 | show_cover4: 17 | gtkwave $(DUT)_cover/engine_0/trace4.vcd $(DUT).gtkw 18 | 19 | clean: 20 | rm -rf $(DUT)_bmc/ 21 | rm -rf $(DUT)_cover/ 22 | rm -rf $(DUT)_prove/ 23 | rm -rf work-obj08.cf 24 | rm -rf yosys.log 25 | rm -rf $(DUT).edif 26 | 27 | # Synthesis 28 | 29 | SOURCES += ../one_stage_buffer/one_stage_buffer.vhd 30 | SOURCES += ../one_stage_fifo/one_stage_fifo.vhd 31 | SOURCES += $(DUT).vhd 32 | 33 | work-obj08.cf: $(SOURCES) 34 | ghdl -a -fpsl -fsynopsys --std=08 $^ 35 | 36 | synth: work-obj08.cf 37 | yosys -m ghdl -p 'ghdl -fpsl -fsynopsys --std=08 $(DUT); synth_xilinx -top $(DUT) -edif $(DUT).edif' > yosys.log 38 | 39 | 40 | -------------------------------------------------------------------------------- /memory/README.md: -------------------------------------------------------------------------------- 1 | # MEMORY module of a yet-to-be-made CPU (optimized version) 2 | 3 | When designing and connecting the different blocks that make up the CPU I find 4 | it convenient to make use of "elastic pipelines", i.e. where the interface 5 | consists of a `valid` signal from source to sink, and a `ready` signal from the 6 | sink back to the source. However, the WISHBONE protocol is not as easy, because 7 | the WISHBONE response signal `wb_ack_i` can not be delayed. 8 | 9 | Therefore, I find it convenient to introduce the MEMORY module, which acts as 10 | an "adapter" from the WISHBONE interface to the "elastic pipeline" interface. 11 | Furthermore, the MEMORY module stores the responses on two different output 12 | pipelines, for use by the CPU. 13 | 14 | ## Interface 15 | 16 | The MEMORY module exposes a source interface (connected to the EXECUTE module) 17 | as follows: 18 | ``` 19 | s_valid_i : in std_logic; 20 | s_ready_o : out std_logic; 21 | s_op_i : in std_logic_vector(2 downto 0); 22 | s_addr_i : in std_logic_vector(15 downto 0); 23 | s_data_i : in std_logic_vector(15 downto 0); 24 | ``` 25 | 26 | The `s_op_i` is a one-hot encoding of the requested operation: 27 | * `C_WRITE` : Write `data` to `addr`. 28 | * `C_READ_DST` : Read from `addr` and place result in `src`. 29 | * `C_READ_SRC` : Read from `addr` and place result in `dst`. 30 | 31 | The `src` and `dst` interfaces mentioned here are the two output pipelines: 32 | ``` 33 | msrc_valid_o : out std_logic; 34 | msrc_ready_i : in std_logic; 35 | msrc_data_o : out std_logic_vector(15 downto 0); 36 | 37 | mdst_valid_o : out std_logic; 38 | mdst_ready_i : in std_logic; 39 | mdst_data_o : out std_logic_vector(15 downto 0); 40 | ``` 41 | 42 | The idea is that the EXECUTE block issues requests and reads back the results 43 | at a later time. 44 | 45 | The main benefit of this module is that it **stores** the results read back from 46 | memory, in case the EXECUTE module is not yet ready to receive them. 47 | 48 | ## Implementation 49 | We want the module to have low latency - ideally a total latency of one clock 50 | cycle - from `s_valid_i` to, say, `msrc_valid_o`. In fact, this is achieved in 51 | this implementation. We'll discuss the data path and the control path 52 | separately. 53 | 54 | ### Data Path 55 | 56 | So first of all, the WISHBONE request interface is driven combinatorially, i.e. 57 | the address and data signals are simply connected directly: 58 | ``` 59 | wb_addr_o <= s_addr_i; 60 | wb_we_o <= s_op_i(C_WRITE); 61 | wb_dat_o <= s_data_i; 62 | ``` 63 | 64 | Secondly, the WISHBONE response `wb_data_i` is connected to two different 65 | instancies of `one_stage_buffer`. 66 | 67 | ``` 68 | i_one_stage_buffer_src : entity work.one_stage_buffer 69 | port map ( 70 | clk_i => clk_i, 71 | rst_i => rst_i, 72 | s_valid_i => osb_src_valid, 73 | s_ready_o => osb_src_ready, 74 | s_data_i => wb_data_i, 75 | m_valid_o => msrc_valid_o, 76 | m_ready_i => msrc_ready_i, 77 | m_data_o => msrc_data_o 78 | ); -- i_one_stage_buffer_src 79 | 80 | i_one_stage_buffer_dst : entity work.one_stage_buffer 81 | port map ( 82 | clk_i => clk_i, 83 | rst_i => rst_i, 84 | s_valid_i => osb_dst_valid, 85 | s_ready_o => osb_dst_ready, 86 | s_data_i => wb_data_i, 87 | m_valid_o => mdst_valid_o, 88 | m_ready_i => mdst_ready_i, 89 | m_data_o => mdst_data_o 90 | ); -- i_one_stage_buffer_dst 91 | ``` 92 | 93 | Notice how the `wb_data_i` signal connects directly to both buffers, and that 94 | the output from these buffers are directly connected to the outputs of this 95 | module. 96 | 97 | We have to be careful though: When the WISHBONE response arrives we must make 98 | sure that the output buffers can accept the data, because the response exists 99 | only for a single clock cycle. We'll get back to this in the section about 100 | formal verification. 101 | 102 | ### Control Path 103 | The control path is responsible for the WISHBONE request control signals, 104 | controlling the two output buffers, and the upstream ready signal. 105 | 106 | Let's review the WISHBONE interface. When a request is made, the `wb_cyc_o` 107 | must be held high until the acknowledge is received. So we need a flag to 108 | indicate whether we're waiting for an acknowledge. This is all achieved by the 109 | following: 110 | 111 | ``` 112 | wb_cyc_o <= ((s_valid_i and s_ready_o) or wait_for_ack) and not rst_i; 113 | wb_stb_o <= wb_cyc_o and s_valid_i and s_ready_o; 114 | 115 | p_wait_for_ack : process (clk_i) 116 | begin 117 | if rising_edge(clk_i) then 118 | if wb_cyc_o and wb_ack_i then 119 | wait_for_ack <= '0'; 120 | end if; 121 | 122 | if wb_cyc_o and wb_stb_o and not wb_stall_i then 123 | wait_for_ack <= '1'; 124 | end if; 125 | 126 | if rst_i = '1' then 127 | wait_for_ack <= '0'; 128 | end if; 129 | end if; 130 | end process p_wait_for_ack; 131 | ``` 132 | 133 | Both read and write requests are always followed by a corresponding acknowledge 134 | signal. It's not possible to perform read and write simultaneously. However, 135 | when an acknowledge signal arrives there is no indication of which request it 136 | originated from. This means we must keep track of the requests sent, in 137 | particular the read requests. 138 | 139 | Therefore we instantiate a `one_stage_fifo` as well, to keep track of this 140 | information. This fifo only keeps track of read requests and only contains a 141 | single bit to distinguish whether the result should be stored in the SRC or DST 142 | pipeline. 143 | 144 | ``` 145 | osf_mem_in_valid <= s_valid_i and s_ready_o and (s_op_i(C_READ_SRC) or s_op_i(C_READ_DST)); 146 | 147 | i_one_stage_fifo_mem : entity work.one_stage_fifo 148 | generic map ( 149 | G_DATA_SIZE => 1 150 | ) 151 | port map ( 152 | clk_i => clk_i, 153 | rst_i => rst_i, 154 | s_valid_i => osf_mem_in_valid, 155 | s_ready_o => osf_mem_in_ready, 156 | s_data_i(0) => s_op_i(C_READ_SRC), 157 | m_valid_o => osf_mem_out_valid, 158 | m_ready_i => wb_cyc_o and wb_ack_i, 159 | m_data_o(0) => osf_mem_out_data 160 | ); -- i_one_stage_fifo_mem 161 | ``` 162 | 163 | The above shows that whenever a read request is accepted, the request is 164 | stored in the fifo. Furthermore, when an acknowledge is received from the 165 | WISHBONE, then we read out the request information from the fifo. 166 | 167 | Using this information we can now control writing into the two output buffers: 168 | 169 | ``` 170 | osb_src_valid <= wb_cyc_o and wb_ack_i and osf_mem_out_valid and osf_mem_out_data; 171 | osb_dst_valid <= wb_cyc_o and wb_ack_i and osf_mem_out_valid and not osf_mem_out_data; 172 | ``` 173 | 174 | The final part is to control the `s_ready_o` upstream signal. The main 175 | limitation is that each output buffer can hold only one value. So if the 176 | request is a read to SRC, and the output SRC buffer contains data that it can't 177 | deliver, then we must wait. Similarly for DST. 178 | 179 | This leads to the following logic: 180 | ``` 181 | s_ready_o <= not (s_op_i(C_READ_SRC) and msrc_valid_o and not msrc_ready_i) 182 | and not (s_op_i(C_READ_DST) and mdst_valid_o and not mdst_ready_i); 183 | ``` 184 | 185 | Notice how the upstream ready signal depends on the contents of the downstream 186 | `s_op_i` signal. This is non-standard, but allows the module to accept a read 187 | DST even though the SRC output is still waiting. 188 | 189 | ## Formal verification 190 | 191 | ### Internal assertions 192 | We mentioned above that a requirement for this design to work is that the 193 | output buffers are always ready to accept a response from the WISHBONE. 194 | Therefore, we being with the following two properties: 195 | 196 | ``` 197 | f_osb_src_overflow : assert always {osb_src_in_valid and not rst_i} |-> {osb_src_in_ready}; 198 | f_osb_dst_overflow : assert always {osb_dst_in_valid and not rst_i} |-> {osb_dst_in_ready}; 199 | ``` 200 | 201 | ### Assumptions about inputs 202 | 203 | First of all we must ensure the correct format of the input operation: 204 | ``` 205 | f_exe_op : assume always {s_valid_i} |-> {s_op_i = "001" or s_op_i = "010" or s_op_i = "100"}; 206 | ``` 207 | 208 | ### Cover statements 209 | To demonstrate the correct functionality of the module, let's add a cover 210 | statement. This will verify that the module can output four values 211 | back-to-back, alternating on the SRC and DST interfaces. Additinally, we 212 | restrict the ready signals correspondingly: 213 | ``` 214 | f_cover_burst2 : cover {msrc_valid_o and msrc_ready_i and not mdst_ready_i; 215 | mdst_valid_o and mdst_ready_i and not msrc_ready_i; 216 | msrc_valid_o and msrc_ready_i and not mdst_ready_i; 217 | mdst_valid_o and mdst_ready_i and not msrc_ready_i}; 218 | ``` 219 | 220 | ## Running formal verification 221 | ![Waveform](waveform.png) 222 | 223 | ## Synthesis 224 | ``` 225 | Number of cells: 217 226 | BUFG 1 227 | FDRE 37 228 | IBUF 58 229 | LUT2 4 230 | LUT3 40 231 | LUT4 1 232 | LUT5 1 233 | LUT6 4 234 | MUXF7 1 235 | OBUF 70 236 | 237 | Estimated number of LCs: 46 238 | ``` 239 | 240 | -------------------------------------------------------------------------------- /memory/memory.gtkw: -------------------------------------------------------------------------------- 1 | [*] 2 | [*] GTKWave Analyzer v3.3.103 (w)1999-2019 BSI 3 | [*] Sat Jan 30 18:51:40 2021 4 | [*] 5 | [dumpfile] "/home/mike/git/MJoergen/formal/memory/memory_cover/engine_0/trace4.vcd" 6 | [dumpfile_mtime] "Sat Jan 30 18:51:09 2021" 7 | [dumpfile_size] 6451 8 | [savefile] "/home/mike/git/MJoergen/formal/memory/memory.gtkw" 9 | [timestart] 0 10 | [size] 1512 1050 11 | [pos] 0 11 12 | *-3.688748 16 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 13 | [treeopen] memory. 14 | [sst_width] 275 15 | [signals_width] 331 16 | [sst_expanded] 1 17 | [sst_vpaned_height] 298 18 | @28 19 | memory.clk_i 20 | memory.rst_i 21 | [color] 2 22 | memory.s_valid_i 23 | [color] 6 24 | memory.s_ready_o 25 | memory.s_op_i[2:0] 26 | @22 27 | memory.s_addr_i[15:0] 28 | memory.s_data_i[15:0] 29 | @28 30 | [color] 2 31 | memory.wb_cyc_o 32 | [color] 2 33 | memory.wb_stb_o 34 | [color] 6 35 | memory.wb_stall_i 36 | memory.wb_we_o 37 | @22 38 | memory.wb_addr_o[15:0] 39 | memory.wb_dat_o[15:0] 40 | @28 41 | [color] 2 42 | memory.wb_ack_i 43 | @22 44 | memory.wb_data_i[15:0] 45 | @28 46 | [color] 2 47 | memory.mdst_valid_o 48 | [color] 6 49 | memory.mdst_ready_i 50 | @22 51 | memory.mdst_data_o[15:0] 52 | @28 53 | [color] 2 54 | memory.msrc_valid_o 55 | [color] 6 56 | memory.msrc_ready_i 57 | @22 58 | memory.msrc_data_o[15:0] 59 | @200 60 | - 61 | @28 62 | memory.wait_for_ack 63 | [color] 2 64 | memory.osb_mem_in_valid 65 | [color] 6 66 | memory.osb_mem_in_ready 67 | [color] 2 68 | memory.osb_mem_out_valid 69 | [color] 2 70 | memory.osb_src_valid 71 | [color] 6 72 | memory.osb_src_ready 73 | [color] 2 74 | memory.osb_dst_valid 75 | [color] 6 76 | memory.osb_dst_ready 77 | @200 78 | - 79 | @28 80 | memory.i_memory.f_wb_req_count[1:0] 81 | memory.i_memory.f_wb_stall_delay[1:0] 82 | memory.i_memory.f_wb_ack_delay[1:0] 83 | memory.i_memory.f_exe_src_req_count[1:0] 84 | memory.i_memory.f_exe_dst_req_count[1:0] 85 | [pattern_trace] 1 86 | [pattern_trace] 0 87 | -------------------------------------------------------------------------------- /memory/memory.psl: -------------------------------------------------------------------------------- 1 | vunit i_memory(memory(synthesis)) 2 | { 3 | 4 | signal f_wb_req_count : integer range 0 to 3 := 0; 5 | signal f_wb_stall_delay : integer range 0 to 3 := 0; 6 | signal f_wb_ack_delay : integer range 0 to 3 := 0; 7 | signal f_exe_src_req_count : integer range 0 to 3 := 0; 8 | signal f_exe_dst_req_count : integer range 0 to 3 := 0; 9 | 10 | -- set all declarations to run on clk 11 | default clock is rising_edge(clk_i); 12 | 13 | 14 | -------------------------------------------- 15 | -- INTERNAL ASSERTIONS 16 | -------------------------------------------- 17 | 18 | -- The two output buffers should never overflow. 19 | f_osb_src_overflow : assert always {osb_src_in_valid and not rst_i} |-> {osb_src_in_ready}; 20 | f_osb_dst_overflow : assert always {osb_dst_in_valid and not rst_i} |-> {osb_dst_in_ready}; 21 | 22 | -- The input to the buffers should be stable. 23 | -- f_osb_src_stable : assert always {osb_src_valid and not osb_src_ready} |=> {stable(osb_src_valid) and stable (wb_data_i)}; 24 | -- f_osb_dst_stable : assert always {osb_dst_valid and not osb_dst_ready} |=> {stable(osb_dst_valid) and stable (wb_data_i)}; 25 | 26 | 27 | ------------------------------------------------ 28 | -- PROPERTIES OF THE WISHBONE MASTER INTERFACE 29 | ------------------------------------------------ 30 | 31 | -- Count the number of outstanding WISHBONE requests 32 | p_wb_req_count : process (clk_i) 33 | begin 34 | if rising_edge(clk_i) then 35 | -- Request without response 36 | if (wb_cyc_o and wb_stb_o and not wb_stall_i) and not (wb_cyc_o and wb_ack_i) then 37 | f_wb_req_count <= f_wb_req_count + 1; 38 | end if; 39 | 40 | -- Reponse without request 41 | if not(wb_cyc_o and wb_stb_o and not wb_stall_i) and (wb_cyc_o and wb_ack_i) then 42 | f_wb_req_count <= f_wb_req_count - 1; 43 | end if; 44 | 45 | -- If CYC goes low mid-transaction, the transaction is aborted. 46 | if rst_i or not wb_cyc_o then 47 | f_wb_req_count <= 0; 48 | end if; 49 | end if; 50 | end process p_wb_req_count; 51 | 52 | 53 | -- WISHBONE MASTER: Clear all requests while in reset 54 | f_wb_master_reset : assert always {rst_i} |-> {not wb_cyc_o and not wb_stb_o}; 55 | 56 | -- WISHBONE MASTER: STB must be low when CYC is low. 57 | f_wb_master_stb_low : assert always {not wb_cyc_o} |-> {not wb_stb_o}; 58 | 59 | -- WISHBONE MASTER: While a request is stalled it cannot change, except on reset or abort. 60 | -- f_wb_master_stable : assert always {wb_stb_o and wb_stall_i and not rst_i} |=> {stable(wb_stb_o) and stable(wb_addr_o)}; 61 | 62 | 63 | ----------------------------- 64 | -- ASSUMPTIONS ABOUT INPUTS 65 | ----------------------------- 66 | 67 | -- Count the number of outstanding EXECUTE SRC requests 68 | p_exe_src_req_count : process (clk_i) 69 | variable up : std_logic; 70 | variable down : std_logic; 71 | begin 72 | if rising_edge(clk_i) then 73 | up := s_valid_i and s_ready_o and s_op_i(C_READ_SRC); 74 | down := msrc_valid_o and msrc_ready_i; 75 | 76 | -- Request without response 77 | if up and not down then 78 | f_exe_src_req_count <= f_exe_src_req_count + 1; 79 | end if; 80 | 81 | if down and not up then 82 | f_exe_src_req_count <= f_exe_src_req_count - 1; 83 | end if; 84 | 85 | if rst_i then 86 | f_exe_src_req_count <= 0; 87 | end if; 88 | end if; 89 | end process p_exe_src_req_count; 90 | 91 | -- Count the number of outstanding EXECUTE DST requests 92 | p_exe_dst_req_count : process (clk_i) 93 | variable up : std_logic; 94 | variable down : std_logic; 95 | begin 96 | if rising_edge(clk_i) then 97 | up := s_valid_i and s_ready_o and s_op_i(C_READ_DST); 98 | down := mdst_valid_o and mdst_ready_i; 99 | 100 | -- Request without response 101 | if up and not down then 102 | f_exe_dst_req_count <= f_exe_dst_req_count + 1; 103 | end if; 104 | 105 | if down and not up then 106 | f_exe_dst_req_count <= f_exe_dst_req_count - 1; 107 | end if; 108 | 109 | if rst_i then 110 | f_exe_dst_req_count <= 0; 111 | end if; 112 | end if; 113 | end process p_exe_dst_req_count; 114 | 115 | -- Count the number of clock cycles the WISHBONE SLAVE stalls 116 | p_wb_stall_delay : process (clk_i) 117 | begin 118 | if rising_edge(clk_i) then 119 | -- Stalled request 120 | if wb_cyc_o and wb_stb_o and wb_stall_i then 121 | f_wb_stall_delay <= f_wb_stall_delay + 1; 122 | else 123 | f_wb_stall_delay <= 0; 124 | end if; 125 | end if; 126 | end process p_wb_stall_delay; 127 | 128 | -- Count the number of clock cycles the WISHBONE SLAVE waits before responding 129 | p_wb_ack_delay : process (clk_i) 130 | begin 131 | if rising_edge(clk_i) then 132 | -- Transaction without response 133 | if (f_wb_req_count > 0 or (wb_cyc_o = '1' and wb_stb_o = '1' and wb_stall_i = '0')) and wb_cyc_o = '1' and wb_ack_i = '0' then 134 | f_wb_ack_delay <= f_wb_ack_delay + 1; 135 | else 136 | f_wb_ack_delay <= 0; 137 | end if; 138 | end if; 139 | end process p_wb_ack_delay; 140 | 141 | 142 | -- Require reset at startup. 143 | f_reset : assume {rst_i}; 144 | 145 | -- WISHBONE SLAVE: No ACKs without a request 146 | f_wb_slave_ack_idle : assume always {f_wb_req_count = 0} |-> {not wb_ack_i}; 147 | 148 | -- WISHBONE SLAVE: Only stall for at most 2 clock cycles. This is an artifical constraint. 149 | f_wb_slave_stall_delay_max : assume always {f_wb_stall_delay <= 2}; 150 | 151 | -- WISHBONE SLAVE: Respond within at most 2 clock cycles. This is an artifical constraint. 152 | f_wb_slave_ack_delay_max : assume always {f_wb_ack_delay <= 2}; 153 | 154 | -- EXECUTE: Restrict valid inputs 155 | f_exe_op : assume always {s_valid_i} |-> {s_op_i = "001" or s_op_i = "010" or s_op_i = "100"}; 156 | 157 | -- EXECUTE: Restrict number of requests 158 | f_exe_src_req_count_max : assume always {f_exe_src_req_count <= 2}; 159 | f_exe_dst_req_count_max : assume always {f_exe_dst_req_count <= 2}; 160 | 161 | 162 | -------------------------------------------- 163 | -- COVER STATEMENTS TO VERIFY REACHABILITY 164 | -------------------------------------------- 165 | 166 | f_cover_burst2 : cover {msrc_valid_o and msrc_ready_i and not mdst_ready_i; 167 | mdst_valid_o and mdst_ready_i and not msrc_ready_i; 168 | msrc_valid_o and msrc_ready_i and not mdst_ready_i; 169 | mdst_valid_o and mdst_ready_i and not msrc_ready_i}; 170 | 171 | -- OUTPUT src data 172 | f_cover_out_src : cover {msrc_valid_o}; 173 | 174 | -- OUTPUT dst data 175 | f_cover_out_dst : cover {mdst_valid_o}; 176 | 177 | f_cover_burst : cover {rst_i = '0'; 178 | s_valid_i = '1' and s_ready_o = '1' and s_op_i = "100"; 179 | s_valid_i = '1' and s_ready_o = '1' and s_op_i = "010"; 180 | s_valid_i = '1' and s_ready_o = '1' and s_op_i = "001"}; 181 | 182 | 183 | ---------------------------------------------- 184 | -- ADDITIONAL ASSERTS NEEDED FOR K-INDUCTION 185 | ---------------------------------------------- 186 | 187 | 188 | ---------------------------------------------- 189 | -- ADDITIONAL ASSUMES HELPFUL WHEN DEBUGGING 190 | ---------------------------------------------- 191 | 192 | -- assume always {not rst_i} |=> {not rst_i}; 193 | 194 | } -- vunit i_memory(memory(synthesis)) 195 | 196 | -------------------------------------------------------------------------------- /memory/memory.sby: -------------------------------------------------------------------------------- 1 | [tasks] 2 | bmc 3 | cover 4 | 5 | [options] 6 | bmc: mode bmc 7 | bmc: depth 10 8 | cover: mode cover 9 | cover: depth 10 10 | 11 | [engines] 12 | smtbmc 13 | 14 | [script] 15 | ghdl --std=08 memory.vhd memory.psl one_stage_buffer.vhd one_stage_fifo.vhd -e memory 16 | prep -top memory 17 | 18 | [files] 19 | memory.vhd 20 | memory.psl 21 | ../one_stage_buffer/one_stage_buffer.vhd 22 | ../one_stage_fifo/one_stage_fifo.vhd 23 | 24 | -------------------------------------------------------------------------------- /memory/memory.vhd: -------------------------------------------------------------------------------- 1 | library ieee; 2 | use ieee.std_logic_1164.all; 3 | 4 | entity memory is 5 | port ( 6 | clk_i : in std_logic; 7 | rst_i : in std_logic; 8 | 9 | -- From execute 10 | s_valid_i : in std_logic; 11 | s_ready_o : out std_logic; 12 | s_op_i : in std_logic_vector(2 downto 0); 13 | s_addr_i : in std_logic_vector(15 downto 0); 14 | s_data_i : in std_logic_vector(15 downto 0); 15 | 16 | -- To execute 17 | msrc_valid_o : out std_logic; 18 | msrc_ready_i : in std_logic; 19 | msrc_data_o : out std_logic_vector(15 downto 0); 20 | 21 | mdst_valid_o : out std_logic; 22 | mdst_ready_i : in std_logic; 23 | mdst_data_o : out std_logic_vector(15 downto 0); 24 | 25 | -- Memory 26 | wb_cyc_o : out std_logic; 27 | wb_stb_o : out std_logic; 28 | wb_stall_i : in std_logic; 29 | wb_addr_o : out std_logic_vector(15 downto 0); 30 | wb_we_o : out std_logic; 31 | wb_dat_o : out std_logic_vector(15 downto 0); 32 | wb_ack_i : in std_logic; 33 | wb_data_i : in std_logic_vector(15 downto 0) 34 | ); 35 | end entity memory; 36 | 37 | architecture synthesis of memory is 38 | 39 | constant C_READ_SRC : integer := 2; 40 | constant C_READ_DST : integer := 1; 41 | constant C_WRITE : integer := 0; 42 | 43 | signal osf_mem_in_valid : std_logic; 44 | signal osf_mem_in_ready : std_logic; 45 | signal osf_mem_out_valid : std_logic; 46 | signal osf_mem_out_ready : std_logic; 47 | signal osf_mem_out_data : std_logic; 48 | 49 | signal osb_src_in_valid : std_logic; 50 | signal osb_src_in_ready : std_logic; 51 | signal osb_dst_in_valid : std_logic; 52 | signal osb_dst_in_ready : std_logic; 53 | 54 | signal wait_for_ack : std_logic; 55 | 56 | begin 57 | 58 | s_ready_o <= not (s_op_i(C_READ_SRC) and msrc_valid_o and not msrc_ready_i) 59 | and not (s_op_i(C_READ_DST) and mdst_valid_o and not mdst_ready_i); 60 | 61 | 62 | -- WISHBONE request interface is combinatorial 63 | wb_cyc_o <= ((s_valid_i and s_ready_o) or wait_for_ack) and not rst_i; 64 | wb_stb_o <= wb_cyc_o and s_valid_i and s_ready_o; 65 | wb_addr_o <= s_addr_i; 66 | wb_we_o <= s_op_i(C_WRITE); 67 | wb_dat_o <= s_data_i; 68 | 69 | p_wait_for_ack : process (clk_i) 70 | begin 71 | if rising_edge(clk_i) then 72 | if wb_cyc_o and wb_ack_i then 73 | wait_for_ack <= '0'; 74 | end if; 75 | 76 | if wb_cyc_o and wb_stb_o and not wb_stall_i then 77 | wait_for_ack <= '1'; 78 | end if; 79 | 80 | if rst_i = '1' then 81 | wait_for_ack <= '0'; 82 | end if; 83 | end if; 84 | end process p_wait_for_ack; 85 | 86 | 87 | ---------------------- 88 | -- Store the request 89 | ---------------------- 90 | 91 | osf_mem_in_valid <= s_valid_i and s_ready_o and (s_op_i(C_READ_SRC) or s_op_i(C_READ_DST)); 92 | 93 | i_one_stage_fifo_mem : entity work.one_stage_fifo 94 | generic map ( 95 | G_DATA_SIZE => 1 96 | ) 97 | port map ( 98 | clk_i => clk_i, 99 | rst_i => rst_i, 100 | s_valid_i => osf_mem_in_valid, 101 | s_ready_o => osf_mem_in_ready, 102 | s_data_i(0) => s_op_i(C_READ_SRC), 103 | m_valid_o => osf_mem_out_valid, 104 | m_ready_i => wb_cyc_o and wb_ack_i, 105 | m_data_o(0) => osf_mem_out_data 106 | ); -- i_one_stage_fifo_mem 107 | 108 | 109 | ------------------------------------------ 110 | -- Store the response for the SRC output 111 | ------------------------------------------ 112 | 113 | osb_src_in_valid <= wb_ack_i and osf_mem_out_valid and osf_mem_out_data; 114 | 115 | i_one_stage_buffer_src : entity work.one_stage_buffer 116 | generic map ( 117 | G_DATA_SIZE => 16 118 | ) 119 | port map ( 120 | clk_i => clk_i, 121 | rst_i => rst_i, 122 | s_valid_i => osb_src_in_valid, 123 | s_ready_o => osb_src_in_ready, 124 | s_data_i => wb_data_i, 125 | m_valid_o => msrc_valid_o, 126 | m_ready_i => msrc_ready_i, 127 | m_data_o => msrc_data_o 128 | ); -- i_one_stage_buffer_src 129 | 130 | 131 | ------------------------------------------ 132 | -- Store the response for the DST output 133 | ------------------------------------------ 134 | 135 | osb_dst_in_valid <= wb_ack_i and osf_mem_out_valid and not osf_mem_out_data; 136 | 137 | i_one_stage_buffer_dst : entity work.one_stage_buffer 138 | generic map ( 139 | G_DATA_SIZE => 16 140 | ) 141 | port map ( 142 | clk_i => clk_i, 143 | rst_i => rst_i, 144 | s_valid_i => osb_dst_in_valid, 145 | s_ready_o => osb_dst_in_ready, 146 | s_data_i => wb_data_i, 147 | m_valid_o => mdst_valid_o, 148 | m_ready_i => mdst_ready_i, 149 | m_data_o => mdst_data_o 150 | ); -- i_one_stage_buffer_dst 151 | 152 | end architecture synthesis; 153 | 154 | -------------------------------------------------------------------------------- /memory/waveform.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MJoergen/formal/9e0bc6c20361925199b3a94eb3dd362aba29b010/memory/waveform.png -------------------------------------------------------------------------------- /one_stage_buffer/Makefile: -------------------------------------------------------------------------------- 1 | # Type 'make formal' to run formal verification 2 | # Type 'make synth' to run synthesis 3 | 4 | DUT = one_stage_buffer 5 | SRC += $(DUT).vhd 6 | 7 | 8 | ####################### 9 | # Formal verification 10 | ####################### 11 | 12 | .PHONY: formal 13 | formal: $(DUT)_cover/PASS $(DUT)_prove/PASS 14 | $(DUT)_cover/PASS: $(DUT).sby $(DUT).psl $(SRC) 15 | # This is the main command line to run the formal verification 16 | sby --yosys "yosys -m ghdl" -f $(DUT).sby 17 | 18 | show_prove: 19 | gtkwave $(DUT)_prove/engine_0/trace_induct.vcd $(DUT).gtkw 20 | 21 | 22 | ####################### 23 | # Synthesis 24 | ####################### 25 | 26 | .PHONY: synth 27 | synth: work-obj08.cf 28 | yosys -m ghdl -p 'ghdl -fpsl -fsynopsys --std=08 $(DUT); synth_xilinx -top $(DUT) -edif $(DUT).edif' > yosys.log 29 | 30 | work-obj08.cf: $(SRC) 31 | ghdl -a -fpsl -fsynopsys --std=08 $^ 32 | 33 | 34 | ####################### 35 | # Cleanup 36 | ####################### 37 | 38 | .PHONY: clean 39 | clean: 40 | rm -rf $(DUT)_cover/ 41 | rm -rf $(DUT)_prove/ 42 | rm -rf work-obj08.cf 43 | rm -rf yosys.log 44 | rm -rf $(DUT).edif 45 | 46 | -------------------------------------------------------------------------------- /one_stage_buffer/README.md: -------------------------------------------------------------------------------- 1 | # Another simple module : One Stage Buffer 2 | This module is very similar to the [one_stage_fifo](../one_stage_fifo) we just 3 | completed. In particular, the port signals are the same as for 4 | `one_stage_fifo`, but the functionality is slightly different. 5 | 6 | The requirements here are that this buffer works as a combinatorial wire 7 | between the sender and the receiver. 8 | In other words, where the FIFO would have 9 | a latency of one clock cycle from input to output, then this BUFFER has zero 10 | latency: Valid input data is available on the output signals in the same clock 11 | cycle. 12 | 13 | Howeve, if the receiver is not ready, then this module will store the data for 14 | later. This module is useful e.g. in situations where the sender does not 15 | support back-pressure. 16 | 17 | The differences between the two modules are very small and subtle, and I've 18 | tried to keep the implementations as close as possible. 19 | 20 | ## Running formal verification 21 | 22 | Once again, to run the formal verification, just type `make`. The result of 23 | the cover statement is seen below: 24 | ![Waveform](cover_statement.png) 25 | 26 | ## Synthesis 27 | 28 | And the result of synthesis is shown here: 29 | ``` 30 | Number of wires: 34 31 | Number of wire bits: 74 32 | Number of public wires: 11 33 | Number of public wire bits: 32 34 | Number of memories: 0 35 | Number of memory bits: 0 36 | Number of processes: 0 37 | Number of cells: 44 38 | BUFG 1 39 | FDRE 9 40 | IBUF 12 41 | LUT2 1 42 | LUT3 11 43 | OBUF 10 44 | 45 | Estimated number of LCs: 11 46 | ``` 47 | 48 | -------------------------------------------------------------------------------- /one_stage_buffer/cover_statement.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MJoergen/formal/9e0bc6c20361925199b3a94eb3dd362aba29b010/one_stage_buffer/cover_statement.png -------------------------------------------------------------------------------- /one_stage_buffer/one_stage_buffer.gtkw: -------------------------------------------------------------------------------- 1 | [*] 2 | [*] GTKWave Analyzer v3.3.103 (w)1999-2019 BSI 3 | [*] Wed Jan 6 20:11:16 2021 4 | [*] 5 | [dumpfile] "/home/mike/git/MJoergen/formal/one_stage_buffer/one_stage_buffer_cover/engine_0/trace5.vcd" 6 | [dumpfile_mtime] "Wed Jan 6 20:10:51 2021" 7 | [dumpfile_size] 1216 8 | [savefile] "/home/mike/git/MJoergen/formal/one_stage_buffer/one_stage_buffer.gtkw" 9 | [timestart] 0 10 | [size] 1920 1011 11 | [pos] -1 -1 12 | *-3.308034 26 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 13 | [treeopen] one_stage_buffer. 14 | [sst_width] 249 15 | [signals_width] 266 16 | [sst_expanded] 1 17 | [sst_vpaned_height] 286 18 | @28 19 | one_stage_buffer.clk_i 20 | one_stage_buffer.rst_i 21 | [color] 2 22 | one_stage_buffer.s_valid_i 23 | [color] 7 24 | one_stage_buffer.s_ready_o 25 | @22 26 | one_stage_buffer.s_data_i[7:0] 27 | @28 28 | [color] 2 29 | one_stage_buffer.m_valid_o 30 | [color] 7 31 | one_stage_buffer.m_ready_i 32 | @22 33 | one_stage_buffer.m_data_o[7:0] 34 | @200 35 | - 36 | @28 37 | one_stage_buffer.m_valid_r 38 | @200 39 | - 40 | @24 41 | one_stage_buffer.i_one_stage_buffer.f_count[1:0] 42 | [pattern_trace] 1 43 | [pattern_trace] 0 44 | -------------------------------------------------------------------------------- /one_stage_buffer/one_stage_buffer.psl: -------------------------------------------------------------------------------- 1 | vunit i_one_stage_buffer (one_stage_buffer(synthesis)) 2 | { 3 | -- Additional signals used during formal verification 4 | signal f_last_value : std_logic_vector(G_DATA_SIZE-1 downto 0) := (others => '0'); 5 | signal f_count : integer range 0 to 3 := 0; 6 | 7 | -- set all declarations to run on clk_i 8 | default clock is rising_edge(clk_i); 9 | 10 | 11 | ----------------------------- 12 | -- ASSERTIONS ABOUT OUTPUTS 13 | ----------------------------- 14 | 15 | -- BUFFER must be empty after reset, unless new incoming data 16 | f_after_reset : assert always {rst_i} |=> {not m_valid_o} abort s_valid_i; 17 | 18 | -- Output must be stable until accepted or reset 19 | f_output_stable : assert always {m_valid_o and not m_ready_i and not rst_i} |=> {stable(m_valid_o) and stable(m_data_o)}; 20 | 21 | -- Ready must be stable until new data 22 | f_ready_stable : assert always {s_ready_o and not s_valid_i and not rst_i} |=> {stable(s_ready_o)}; 23 | 24 | -- Keep track of amount of data flowing into and out of the BUFFER 25 | p_count : process (clk_i) 26 | begin 27 | if rising_edge(clk_i) then 28 | -- Data flowing in, but not out. 29 | if s_valid_i and s_ready_o and not (m_valid_o and m_ready_i) then 30 | f_count <= f_count + 1; 31 | end if; 32 | 33 | -- Data flowing out, but not in. 34 | if m_valid_o and m_ready_i and not (s_valid_i and s_ready_o) then 35 | f_count <= f_count - 1; 36 | end if; 37 | 38 | if rst_i then 39 | f_count <= 0; 40 | end if; 41 | end if; 42 | end process p_count; 43 | 44 | -- Keep track of data flowing into and out of the BUFFER 45 | p_last_value : process (clk_i) 46 | begin 47 | if rising_edge(clk_i) then 48 | -- Remember last value written into BUFFER 49 | if s_valid_i and s_ready_o then 50 | f_last_value <= s_data_i; 51 | end if; 52 | end if; 53 | end process p_last_value; 54 | 55 | -- The BUFFER size is limited to 1. 56 | f_size : assert always {0 <= f_count and f_count <= 1}; 57 | 58 | -- If BUFFER is full, it must always present valid data on output 59 | f_count_1 : assert always {f_count = 1} |-> {m_valid_o = '1' and m_data_o = f_last_value} abort rst_i; 60 | 61 | -- If BUFFER is empty and no input, no data present on output 62 | f_count_0 : assert always {f_count = 0 and s_valid_i = '0'} |-> {m_valid_o = '0'} abort rst_i; 63 | 64 | -- If BUFFER is empty and with input, valid data present on output 65 | f_count_0_input : assert always {f_count = 0 and s_valid_i = '1'} |-> {m_valid_o = '1' and m_data_o = s_data_i} abort rst_i; 66 | 67 | -- If BUFFER is empty and no input, no data present on output 68 | f_state_0 : assert always {f_count = 0} |-> {s_afull_o = '0'} abort rst_i; 69 | 70 | -- If BUFFER is empty and no input, no data present on output 71 | f_state_1 : assert always {f_count = 1} |-> {s_afull_o = '1'} abort rst_i; 72 | 73 | 74 | ----------------------------- 75 | -- ASSUMPTIONS ABOUT INPUTS 76 | ----------------------------- 77 | 78 | -- Require reset at startup. 79 | f_reset : assume {rst_i}; 80 | 81 | 82 | -------------------------------------------- 83 | -- COVER STATEMENTS TO VERIFY REACHABILITY 84 | -------------------------------------------- 85 | 86 | -- Make sure BUFFER can transition from full to empty. 87 | f_full_to_empty : cover {f_count = 1; f_count = 0}; 88 | 89 | } -- vunit i_one_stage_buffer (one_stage_buffer(synthesis)) 90 | 91 | -------------------------------------------------------------------------------- /one_stage_buffer/one_stage_buffer.sby: -------------------------------------------------------------------------------- 1 | [tasks] 2 | cover 3 | prove 4 | 5 | [options] 6 | cover: mode cover 7 | prove: mode prove 8 | prove: depth 4 9 | 10 | [engines] 11 | smtbmc 12 | 13 | [script] 14 | ghdl --std=08 one_stage_buffer.vhd one_stage_buffer.psl -e one_stage_buffer 15 | prep -top one_stage_buffer 16 | 17 | [files] 18 | one_stage_buffer.psl 19 | one_stage_buffer.vhd 20 | 21 | -------------------------------------------------------------------------------- /one_stage_buffer/one_stage_buffer.vhd: -------------------------------------------------------------------------------- 1 | library ieee; 2 | use ieee.std_logic_1164.all; 3 | use ieee.numeric_std.all; 4 | 5 | -- This is a simple buffer that is transparent (combinatorial) when the 6 | -- receiver is ready, but registers the incoming value if not. 7 | 8 | entity one_stage_buffer is 9 | generic ( 10 | G_DATA_SIZE : integer := 8 11 | ); 12 | port ( 13 | clk_i : in std_logic; 14 | rst_i : in std_logic; 15 | s_valid_i : in std_logic; 16 | s_ready_o : out std_logic; 17 | s_data_i : in std_logic_vector(G_DATA_SIZE-1 downto 0); 18 | s_afull_o : out std_logic; 19 | m_valid_o : out std_logic; 20 | m_ready_i : in std_logic; 21 | m_data_o : out std_logic_vector(G_DATA_SIZE-1 downto 0) 22 | ); 23 | end entity one_stage_buffer; 24 | 25 | architecture synthesis of one_stage_buffer is 26 | 27 | signal s_ready_s : std_logic; 28 | signal m_valid_r : std_logic := '0'; 29 | signal m_data_r : std_logic_vector(G_DATA_SIZE-1 downto 0) := (others => '0'); 30 | 31 | begin 32 | 33 | -- We accept data from upstream in two situations: 34 | -- * When FIFO is empty. 35 | -- * When downstream is ready. 36 | -- The latter situation allows simultaneous read and write, even when the 37 | -- FIFO is full. 38 | s_ready_s <= m_ready_i or not m_valid_r; 39 | 40 | p_buffer : process (clk_i) 41 | begin 42 | if rising_edge(clk_i) then 43 | -- Downstream has consumed the output 44 | if m_ready_i = '1' and s_valid_i = '0' then 45 | m_valid_r <= '0'; 46 | end if; 47 | 48 | -- Valid data on the input 49 | if s_ready_s = '1' and s_valid_i = '1' then 50 | m_data_r <= s_data_i; 51 | end if; 52 | 53 | -- Store in buffer 54 | if m_ready_i = '0' and s_valid_i = '1' then 55 | m_valid_r <= '1'; 56 | end if; 57 | 58 | -- Reset empties the FIFO 59 | if rst_i = '1' then 60 | m_valid_r <= '0'; 61 | end if; 62 | end if; 63 | end process p_buffer; 64 | 65 | -- Connect output signals 66 | s_afull_o <= m_valid_r; 67 | s_ready_o <= s_ready_s; 68 | m_data_o <= m_data_r when m_valid_r = '1' else s_data_i; 69 | m_valid_o <= m_valid_r or (s_valid_i and not rst_i); 70 | 71 | end architecture synthesis; 72 | 73 | -------------------------------------------------------------------------------- /one_stage_fifo/Makefile: -------------------------------------------------------------------------------- 1 | # Type 'make formal' to run formal verification 2 | # Type 'make synth' to run synthesis 3 | 4 | DUT = one_stage_fifo 5 | SRC += $(DUT).vhd 6 | 7 | 8 | ####################### 9 | # Formal verification 10 | ####################### 11 | 12 | .PHONY: formal 13 | formal: $(DUT)_cover/PASS $(DUT)_prove/PASS 14 | $(DUT)_cover/PASS: $(DUT).sby $(DUT).psl $(SRC) 15 | # This is the main command line to run the formal verification 16 | sby --yosys "yosys -m ghdl" -f $(DUT).sby 17 | 18 | show_prove: 19 | gtkwave $(DUT)_prove/engine_0/trace_induct.vcd $(DUT).gtkw 20 | 21 | 22 | ####################### 23 | # Synthesis 24 | ####################### 25 | 26 | .PHONY: synth 27 | synth: work-obj08.cf 28 | yosys -m ghdl -p 'ghdl -fpsl -fsynopsys --std=08 $(DUT); synth_xilinx -top $(DUT) -edif $(DUT).edif' > yosys.log 29 | 30 | work-obj08.cf: $(SRC) 31 | ghdl -a -fpsl -fsynopsys --std=08 $^ 32 | 33 | 34 | ####################### 35 | # Cleanup 36 | ####################### 37 | 38 | .PHONY: clean 39 | clean: 40 | rm -rf $(DUT)_cover/ 41 | rm -rf $(DUT)_prove/ 42 | rm -rf work-obj08.cf 43 | rm -rf yosys.log 44 | rm -rf $(DUT).edif 45 | 46 | -------------------------------------------------------------------------------- /one_stage_fifo/cover_statement.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MJoergen/formal/9e0bc6c20361925199b3a94eb3dd362aba29b010/one_stage_fifo/cover_statement.png -------------------------------------------------------------------------------- /one_stage_fifo/one_stage_fifo.gtkw: -------------------------------------------------------------------------------- 1 | [*] 2 | [*] GTKWave Analyzer v3.3.103 (w)1999-2019 BSI 3 | [*] Wed Jan 6 19:01:42 2021 4 | [*] 5 | [dumpfile] "/home/mike/git/MJoergen/formal/one_stage_fifo/one_stage_fifo_cover/engine_0/trace2.vcd" 6 | [dumpfile_mtime] "Wed Jan 6 19:00:38 2021" 7 | [dumpfile_size] 1160 8 | [savefile] "/home/mike/git/MJoergen/formal/one_stage_fifo/one_stage_fifo.gtkw" 9 | [timestart] 0 10 | [size] 1376 1011 11 | [pos] 1965 16 12 | *-3.427761 27 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 13 | [treeopen] one_stage_fifo. 14 | [sst_width] 249 15 | [signals_width] 251 16 | [sst_expanded] 1 17 | [sst_vpaned_height] 342 18 | @28 19 | one_stage_fifo.clk_i 20 | one_stage_fifo.rst_i 21 | [color] 2 22 | one_stage_fifo.s_valid_i 23 | [color] 7 24 | one_stage_fifo.s_ready_o 25 | @22 26 | one_stage_fifo.s_data_i[7:0] 27 | @28 28 | [color] 2 29 | one_stage_fifo.m_valid_o 30 | [color] 7 31 | one_stage_fifo.m_ready_i 32 | @22 33 | one_stage_fifo.m_data_o[7:0] 34 | @200 35 | - 36 | @24 37 | one_stage_fifo.i_one_stage_fifo.f_count[1:0] 38 | @22 39 | one_stage_fifo.i_one_stage_fifo.f_last_value[7:0] 40 | [pattern_trace] 1 41 | [pattern_trace] 0 42 | -------------------------------------------------------------------------------- /one_stage_fifo/one_stage_fifo.psl: -------------------------------------------------------------------------------- 1 | vunit i_one_stage_fifo(one_stage_fifo(synthesis)) 2 | { 3 | -- Additional signals used during formal verification 4 | signal f_last_value : std_logic_vector(G_DATA_SIZE-1 downto 0) := (others => '0'); 5 | signal f_count : integer range 0 to 3 := 0; 6 | 7 | 8 | -- set all declarations to run on clk_i 9 | default clock is rising_edge(clk_i); 10 | 11 | 12 | ----------------------------- 13 | -- ASSERTIONS ABOUT OUTPUTS 14 | ----------------------------- 15 | 16 | -- FIFO must be empty after reset 17 | f_after_reset : assert always {rst_i} |=> not m_valid_o; 18 | 19 | -- Output must be stable until accepted 20 | f_output_stable : assert always {m_valid_o and not m_ready_i and not rst_i} |=> {stable(m_valid_o) and stable(m_data_o)}; 21 | 22 | -- Ready must be stable until new data 23 | f_ready_stable : assert always {s_ready_o and not s_valid_i and not rst_i} |=> {stable(s_ready_o)}; 24 | 25 | -- Keep track of amount of data flowing into and out of the FIFO 26 | p_count : process (clk_i) 27 | begin 28 | if rising_edge(clk_i) then 29 | -- Data flowing in, but not out. 30 | if s_valid_i and s_ready_o and not (m_valid_o and m_ready_i) then 31 | f_count <= f_count + 1; 32 | end if; 33 | 34 | -- Data flowing out, but not in. 35 | if m_valid_o and m_ready_i and not (s_valid_i and s_ready_o) then 36 | f_count <= f_count - 1; 37 | end if; 38 | 39 | if rst_i then 40 | f_count <= 0; 41 | end if; 42 | end if; 43 | end process p_count; 44 | 45 | -- Keep track of data flowing into and out of the FIFO 46 | p_last_value : process (clk_i) 47 | begin 48 | if rising_edge(clk_i) then 49 | -- Remember last value written into FIFO 50 | if s_valid_i and s_ready_o then 51 | f_last_value <= s_data_i; 52 | end if; 53 | end if; 54 | end process p_last_value; 55 | 56 | -- The FIFO size is limited to 1. 57 | f_size : assert always {0 <= f_count and f_count <= 1}; 58 | 59 | -- If FIFO is full, it must always present valid data on output 60 | f_count_1 : assert always {f_count = 1} |-> {m_valid_o = '1' and m_data_o = f_last_value} abort rst_i; 61 | 62 | -- If FIFO is empty, no data present on output 63 | f_count_0 : assert always {f_count = 0} |-> {m_valid_o = '0'} abort rst_i; 64 | 65 | 66 | ----------------------------- 67 | -- ASSUMPTIONS ABOUT INPUTS 68 | ----------------------------- 69 | 70 | -- Require reset at startup. 71 | f_reset : assume {rst_i}; 72 | 73 | 74 | -------------------------------------------- 75 | -- COVER STATEMENTS TO VERIFY REACHABILITY 76 | -------------------------------------------- 77 | 78 | -- Make sure FIFO can transition from full to empty. 79 | f_full_to_empty : cover {m_valid_o and not rst_i; not m_valid_o}; 80 | 81 | } -- vunit i_one_stage_buffer(one_stage_buffer(synthesis)) 82 | 83 | -------------------------------------------------------------------------------- /one_stage_fifo/one_stage_fifo.sby: -------------------------------------------------------------------------------- 1 | [tasks] 2 | cover 3 | prove 4 | 5 | [options] 6 | cover: mode cover 7 | prove: mode prove 8 | prove: depth 4 9 | 10 | [engines] 11 | smtbmc 12 | 13 | [script] 14 | ghdl --std=08 one_stage_fifo.vhd one_stage_fifo.psl -e one_stage_fifo 15 | prep -top one_stage_fifo 16 | 17 | [files] 18 | one_stage_fifo.psl 19 | one_stage_fifo.vhd 20 | 21 | -------------------------------------------------------------------------------- /one_stage_fifo/one_stage_fifo.vhd: -------------------------------------------------------------------------------- 1 | library ieee; 2 | use ieee.std_logic_1164.all; 3 | use ieee.numeric_std.all; 4 | 5 | -- This module implements a FIFO consisting of only a single register layer. 6 | -- It has its use in elastic pipelines, where the data flow has back-pressure. 7 | -- It places registers on the valid and data signals in the downstream direction, 8 | -- but the ready signal in the upstream direction is still combinatorial. 9 | -- The FIFO supports simultaneous read and write, both when the FIFO is full 10 | -- and when it is empty. 11 | -- 12 | -- For additional information, see: 13 | -- https://www.itdev.co.uk/blog/pipelining-axi-buses-registered-ready-signals 14 | 15 | entity one_stage_fifo is 16 | generic ( 17 | G_DATA_SIZE : integer := 8 18 | ); 19 | port ( 20 | clk_i : in std_logic; 21 | rst_i : in std_logic; 22 | s_valid_i : in std_logic; 23 | s_ready_o : out std_logic; 24 | s_data_i : in std_logic_vector(G_DATA_SIZE-1 downto 0); 25 | m_valid_o : out std_logic; 26 | m_ready_i : in std_logic; 27 | m_data_o : out std_logic_vector(G_DATA_SIZE-1 downto 0) 28 | ); 29 | end entity one_stage_fifo; 30 | 31 | architecture synthesis of one_stage_fifo is 32 | 33 | signal s_ready_s : std_logic; 34 | signal m_valid_r : std_logic; 35 | signal m_data_r : std_logic_vector(G_DATA_SIZE-1 downto 0); 36 | 37 | begin 38 | 39 | -- We accept data from upstream in two situations: 40 | -- * When FIFO is empty. 41 | -- * When downstream is ready. 42 | -- The latter situation allows simultaneous read and write, even when the 43 | -- FIFO is full. 44 | s_ready_s <= m_ready_i or not m_valid_r; 45 | 46 | p_fifo : process (clk_i) 47 | begin 48 | if rising_edge(clk_i) then 49 | if s_ready_s then 50 | m_data_r <= s_data_i; 51 | m_valid_r <= s_valid_i; 52 | end if; 53 | 54 | -- Reset empties the FIFO 55 | if rst_i = '1' then 56 | m_valid_r <= '0'; 57 | end if; 58 | end if; 59 | end process p_fifo; 60 | 61 | -- Connect output signals 62 | s_ready_o <= s_ready_s; 63 | m_valid_o <= m_valid_r; 64 | m_data_o <= m_data_r; 65 | 66 | end architecture synthesis; 67 | 68 | -------------------------------------------------------------------------------- /pipe_concat/Makefile: -------------------------------------------------------------------------------- 1 | DUT = pipe_concat 2 | 3 | # This is the main command line to run the formal verification 4 | all: 5 | sby --yosys "yosys -m ghdl" -f $(DUT).sby 6 | 7 | show_bmc: 8 | gtkwave $(DUT)_bmc/engine_0/trace.vcd $(DUT).gtkw 9 | 10 | show_cover: 11 | gtkwave $(DUT)_cover/engine_0/trace2.vcd $(DUT).gtkw 12 | 13 | show_prove: 14 | gtkwave $(DUT)_prove/engine_0/trace_induct.vcd $(DUT).gtkw 15 | 16 | clean: 17 | rm -rf $(DUT)_bmc/ 18 | rm -rf $(DUT)_cover/ 19 | rm -rf $(DUT)_prove/ 20 | rm -rf work-obj08.cf 21 | rm -rf yosys.log 22 | rm -rf $(DUT).edif 23 | 24 | # Synthesis 25 | 26 | work-obj08.cf: $(DUT).vhd 27 | ghdl -a -fpsl --std=08 $^ 28 | 29 | synth: work-obj08.cf 30 | yosys -m ghdl -p 'ghdl -fpsl --std=08 $(DUT); synth_xilinx -top $(DUT) -family xc7 -edif $(DUT).edif' > yosys.log 31 | 32 | -------------------------------------------------------------------------------- /pipe_concat/README.md: -------------------------------------------------------------------------------- 1 | # Pipe Concatenation 2 | This very small module is useful, because it concatenates data streams from two 3 | different elastic pipelines. The benefit is that all the mechanics of 4 | controlling the ready signals is abstracted away inside this module. 5 | -------------------------------------------------------------------------------- /pipe_concat/pipe_concat.gtkw: -------------------------------------------------------------------------------- 1 | [*] 2 | [*] GTKWave Analyzer v3.3.103 (w)1999-2019 BSI 3 | [*] Sun Jan 3 08:47:58 2021 4 | [*] 5 | [dumpfile] "/home/mike/git/MJoergen/formal/pipe_concat/pipe_concat_cover/engine_0/trace2.vcd" 6 | [dumpfile_mtime] "Sun Jan 3 08:46:53 2021" 7 | [dumpfile_size] 1009 8 | [savefile] "/home/mike/git/MJoergen/formal/pipe_concat/pipe_concat.gtkw" 9 | [timestart] 0 10 | [size] 1301 776 11 | [pos] -1919 -131 12 | *-2.987028 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 13 | [sst_width] 249 14 | [signals_width] 223 15 | [sst_expanded] 1 16 | [sst_vpaned_height] 205 17 | @28 18 | pipe_concat.s0_valid_i 19 | pipe_concat.s0_ready_o 20 | @22 21 | pipe_concat.s0_data_i[7:0] 22 | @28 23 | pipe_concat.s1_valid_i 24 | pipe_concat.s1_ready_o 25 | @22 26 | pipe_concat.s1_data_i[7:0] 27 | @28 28 | pipe_concat.m_valid_o 29 | pipe_concat.m_ready_i 30 | @23 31 | pipe_concat.m_data_o[15:0] 32 | [pattern_trace] 1 33 | [pattern_trace] 0 34 | -------------------------------------------------------------------------------- /pipe_concat/pipe_concat.psl: -------------------------------------------------------------------------------- 1 | vunit i_pipe_concat(pipe_concat(synthesis)) 2 | { 3 | -- set all declarations to run on clk_i 4 | default clock is rising_edge(clk_i); 5 | 6 | 7 | ----------------------------- 8 | -- ASSERTIONS ABOUT OUTPUTS 9 | ----------------------------- 10 | 11 | f_data_flowm: assert always {m_valid_o and m_ready_i} |-> {s0_valid_i and s0_ready_o}; 12 | f_data_flow0: assert always {s0_valid_i and s0_ready_o}|-> {s1_valid_i and s1_ready_o}; 13 | f_data_flow1: assert always {s1_valid_i and s1_ready_o}|-> {m_valid_o and m_ready_i}; 14 | 15 | f_data: assert always {m_valid_o and m_ready_i} |-> {m_data_o = s1_data_i & s0_data_i}; 16 | 17 | -- Output must be stable until accepted 18 | f_output_stable : assert always {m_valid_o and not m_ready_i and not rst_i} |=> {stable(m_valid_o) and stable(m_data_o)}; 19 | 20 | 21 | ----------------------------- 22 | -- ASSUMPTIONS ABOUT INPUTS 23 | ----------------------------- 24 | 25 | -- Require reset at startup. 26 | f_reset : assume {rst_i}; 27 | 28 | -- Input must be stable until accepted 29 | f_input0_stable : assume always {s0_valid_i and not s0_ready_o and not rst_i} |=> {stable(s0_valid_i) and stable(s0_data_i)}; 30 | f_input1_stable : assume always {s1_valid_i and not s1_ready_o and not rst_i} |=> {stable(s1_valid_i) and stable(s1_data_i)}; 31 | 32 | 33 | -------------------------------------------- 34 | -- COVER STATEMENTS TO VERIFY REACHABILITY 35 | -------------------------------------------- 36 | 37 | -- Make sure output can transition from full to empty. 38 | f_full_to_empty : cover {m_valid_o and not rst_i; not m_valid_o}; 39 | 40 | -- Make sure output can stay valid for two clock cycles. 41 | f_back2back : cover {m_valid_o and m_ready_i; m_valid_o}; 42 | 43 | } -- vunit i_one_stage_buffer(one_stage_buffer(synthesis)) 44 | 45 | -------------------------------------------------------------------------------- /pipe_concat/pipe_concat.sby: -------------------------------------------------------------------------------- 1 | [tasks] 2 | bmc 3 | cover 4 | prove 5 | 6 | [options] 7 | bmc: mode bmc 8 | bmc: depth 4 9 | cover: mode cover 10 | prove: mode prove 11 | prove: depth 4 12 | 13 | [engines] 14 | smtbmc 15 | 16 | [script] 17 | ghdl --std=08 pipe_concat.vhd pipe_concat.psl -e pipe_concat 18 | prep -top pipe_concat 19 | 20 | [files] 21 | pipe_concat.psl 22 | pipe_concat.vhd 23 | 24 | -------------------------------------------------------------------------------- /pipe_concat/pipe_concat.vhd: -------------------------------------------------------------------------------- 1 | -- This concatenates two elastic pipelines into one. 2 | 3 | library ieee; 4 | use ieee.std_logic_1164.all; 5 | 6 | entity pipe_concat is 7 | generic ( 8 | G_DATA0_SIZE : integer := 8; 9 | G_DATA1_SIZE : integer := 8 10 | ); 11 | port ( 12 | clk_i : in std_logic; 13 | rst_i : in std_logic; 14 | s0_valid_i : in std_logic; 15 | s0_ready_o : out std_logic; 16 | s0_data_i : in std_logic_vector(G_DATA0_SIZE-1 downto 0); 17 | s1_valid_i : in std_logic; 18 | s1_ready_o : out std_logic; 19 | s1_data_i : in std_logic_vector(G_DATA1_SIZE-1 downto 0); 20 | m_valid_o : out std_logic; 21 | m_ready_i : in std_logic; 22 | m_data_o : out std_logic_vector(G_DATA0_SIZE+G_DATA1_SIZE-1 downto 0) 23 | ); 24 | end entity pipe_concat; 25 | 26 | architecture synthesis of pipe_concat is 27 | 28 | begin 29 | 30 | m_data_o <= s1_data_i & s0_data_i; 31 | 32 | m_valid_o <= s0_valid_i and s1_valid_i; 33 | s0_ready_o <= m_ready_i and s1_valid_i; 34 | s1_ready_o <= m_ready_i and s0_valid_i; 35 | 36 | end architecture synthesis; 37 | 38 | -------------------------------------------------------------------------------- /rubik/Makefile: -------------------------------------------------------------------------------- 1 | DUT = rubik 2 | 3 | # This is the main command line to run the formal verification 4 | all: 5 | sby --yosys "yosys -m ghdl" -f $(DUT).sby 6 | 7 | show_bmc: 8 | gtkwave $(DUT)_bmc/engine_0/trace.vcd $(DUT).gtkw 9 | 10 | show_cover: 11 | gtkwave $(DUT)_cover/engine_0/trace0.vcd $(DUT).gtkw 12 | 13 | clean: 14 | rm -rf $(DUT)_bmc/ 15 | rm -rf $(DUT)_cover/ 16 | rm -rf work-obj08.cf 17 | rm -rf yosys.log 18 | rm -rf $(DUT).edif 19 | rm -rf $(DUT).ghw 20 | 21 | # Synthesis 22 | 23 | work-obj08.cf: $(DUT).vhd 24 | ghdl -a -fpsl -fsynopsys --std=08 $^ 25 | 26 | synth: work-obj08.cf 27 | yosys -m ghdl -p 'ghdl -fpsl -fsynopsys --std=08 $(DUT); synth_xilinx -top $(DUT) -edif $(DUT).edif' > yosys.log 28 | 29 | # Simulation 30 | 31 | sim: $(DUT).vhd $(DUT)_tb.vhd 32 | ghdl -i -fpsl --std=08 --work=work $^ 33 | ghdl -m -fpsl --std=08 --ieee=synopsys -fexplicit $(DUT)_tb 34 | ghdl -r -fpsl --std=08 --ieee=synopsys $(DUT)_tb --wave=$(DUT).ghw --stop-time=4us 35 | gtkwave $(DUT).ghw $(DUT)_sim.gtkw 36 | 37 | -------------------------------------------------------------------------------- /rubik/README.md: -------------------------------------------------------------------------------- 1 | # Rubik's 2x2x2 cube solver 2 | 3 | This is another fun application of the formal verification tools; 4 | used here to solve the Rubik's 2x2x2 cube. 5 | 6 | ## Implementation 7 | The cube has six faces each with four colors, i.e. a total of 24 tiles. Each 8 | color is selected from a palette of six possible colors. So I've chosen a 9 | simple representation, where I assign each of the 24 tiles a 3-bit value for 10 | the color. 11 | 12 | The faces are called `U` (Up), `F` (Front), `R` (Right), `D` (Down), `B` 13 | (Back), and `L` (Left), and the state of the cube is represented by 14 | these signals: 15 | 16 | ``` 17 | signal u0 : std_logic_vector(2 downto 0); 18 | signal u1 : std_logic_vector(2 downto 0); 19 | signal u2 : std_logic_vector(2 downto 0); 20 | signal u3 : std_logic_vector(2 downto 0); 21 | 22 | signal f0 : std_logic_vector(2 downto 0); 23 | signal f1 : std_logic_vector(2 downto 0); 24 | signal f2 : std_logic_vector(2 downto 0); 25 | signal f3 : std_logic_vector(2 downto 0); 26 | 27 | etc. 28 | ``` 29 | 30 | The tiles are numbered as follows: 31 | ``` 32 | U0 U1 33 | U2 U3 34 | ----- 35 | L0 L1 | F0 F1 | R0 R1 | B0 B1 36 | L2 L3 | F2 F3 | R2 R3 | B2 B3 37 | ----- 38 | D0 D1 39 | D2 D3 40 | ``` 41 | 42 | I've chosen to keep the action on the cube simple. Since the cube is only 43 | 2x2x2, I can limit the possible rotations to only the `U`, `F`, and `R` sides. 44 | So a total of nine possible commands: 45 | 46 | * `C_CMD_FP = F+` 47 | * `C_CMD_F2 = F2` 48 | * `C_CMD_FM = F-` 49 | * `C_CMD_RP = R+` 50 | * `C_CMD_R2 = R2` 51 | * `C_CMD_RM = R-` 52 | * `C_CMD_UP = U+` 53 | * `C_CMD_U2 = U2` 54 | * `C_CMD_UM = U-` 55 | 56 | The interface to [the module](rubik.vhd) is very simple: 57 | 58 | ``` 59 | cmd_i : in std_logic_vector(3 downto 0); 60 | done_o : out std_logic 61 | ``` 62 | 63 | The combinatorial signal `done_o` is asserted when the cube is solved. 64 | 65 | ## Simulation (manual testbench) 66 | For the sake of generating a suitable problem for the solver, I've implemented 67 | a [regular testbench](rubik_tb.vhd). 68 | 69 | This testbench has two purposes. First it verifies the period of each of the 70 | nine possible rotations. So for instance, the rotation `F+` has a period of 4. 71 | This means that repeating the rotation four times should leave cube unchanged. 72 | This is a manual test (i.e not using formal verification) to clear out some of 73 | the typing mistakes in the implementation. 74 | 75 | The second part of the testbench generates a sequence of random commands, 76 | starting from the solved cube. I then manually record the outcome and use it 77 | as initial condition in the [implementation file](rubik.vhd). 78 | 79 | To run the simulation just type 80 | ``` 81 | make sim 82 | ``` 83 | 84 | ## Formal verification 85 | I'm using formal verification here to try to solve the cube. So from the 86 | testbench I have created a random (but legal!) coloring of the cube, and use 87 | that as initial condition when declaring the signals `u0`, `u1`, etc. 88 | 89 | I've added a few assertions to verify the correct functionality of the 90 | implementation. Specifically, I require all eight corner pieces to have three 91 | unique colors. For the first corner it looks like: 92 | ``` 93 | f_edge_u2_f0 : assert always {u2 /= f0}; 94 | f_edge_u2_l1 : assert always {u2 /= l1}; 95 | f_edge_l1_f0 : assert always {l1 /= f0}; 96 | ``` 97 | Similar lines are repeated for the remaining corners. 98 | 99 | Secondly, I require that all the tiles should have equal number of colors. I.e. 100 | the 24 tiles should have four tiles of each of the six colors. This is done 101 | by creating a new signal `f_num_colors` that contains the number of tiles of 102 | each color. Then I simply add the following statement: 103 | ``` 104 | f_colors : assert always {f_num_colors(0 to 5) = (0 to 5 => 4)}; 105 | ``` 106 | 107 | The above were just some extra checks to catch any additional bugs in the 108 | implementation. For solving the cube I simply add the line: 109 | ``` 110 | f_done : cover {done_o}; 111 | ``` 112 | 113 | However, the solver will try to cheat(!) by asserting the reset signal, so this 114 | must be prevented by: 115 | ``` 116 | f_no_rst : assume always {not rst_i}; 117 | ``` 118 | 119 | Now I can simply run 120 | ``` 121 | make 122 | ``` 123 | 124 | The solution takes around a minute to find, and can then be viewed by writing 125 | ``` 126 | make show_cover 127 | ``` 128 | ![Waveform](waveform.png) 129 | 130 | This shows that the cube (from this particular initial condition) can be solved 131 | in a sequence of nine rotations. 132 | 133 | ## Synthesis 134 | Finally, just for fun, we can synthesize the module by typing 135 | ``` 136 | make synth 137 | ``` 138 | The result can be seen below 139 | 140 | ``` 141 | Number of wires: 157 142 | Number of wire bits: 313 143 | Number of public wires: 28 144 | Number of public wire bits: 79 145 | Number of memories: 0 146 | Number of memory bits: 0 147 | Number of processes: 0 148 | Number of cells: 272 149 | BUFG 1 150 | FDRE 40 151 | FDSE 23 152 | IBUF 6 153 | LUT2 1 154 | LUT3 13 155 | LUT5 75 156 | LUT6 69 157 | MUXF7 35 158 | MUXF8 8 159 | OBUF 1 160 | 161 | Estimated number of LCs: 157 162 | ``` 163 | 164 | The interesting thing is that if we add the numbers for `FDRE` and `FDSE` we 165 | get a total of 63 registers. Surely, the cube contains 24 tiles with 3 bits for 166 | each tile, so a total of 72 registers are needed? 167 | 168 | Not so fast! Since we only consider rotations of three of the faces, one of the 169 | corners (`D2,B3,L2`) is completely untouched, and its three tiles never change 170 | color. So that reduces the number of registers by 9. 171 | 172 | -------------------------------------------------------------------------------- /rubik/rubik.gtkw: -------------------------------------------------------------------------------- 1 | [*] 2 | [*] GTKWave Analyzer v3.3.86 (w)1999-2017 BSI 3 | [*] Mon Dec 28 11:32:22 2020 4 | [*] 5 | [dumpfile] "/home/mike/git/MJoergen/formal/rubik/rubik_cover/engine_0/trace1.vcd" 6 | [dumpfile_mtime] "Mon Dec 28 11:31:39 2020" 7 | [dumpfile_size] 4201 8 | [savefile] "/home/mike/git/MJoergen/formal/rubik/rubik.gtkw" 9 | [timestart] 0 10 | [size] 1920 1011 11 | [pos] -1 -1 12 | *-4.045955 16 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 13 | [sst_width] 249 14 | [signals_width] 280 15 | [sst_expanded] 1 16 | [sst_vpaned_height] 286 17 | @28 18 | rubik.clk_i 19 | rubik.rst_i 20 | rubik.cmd_i[3:0] 21 | rubik.done_o 22 | rubik.u0[2:0] 23 | rubik.u1[2:0] 24 | rubik.u2[2:0] 25 | rubik.u3[2:0] 26 | rubik.f0[2:0] 27 | rubik.f1[2:0] 28 | rubik.f2[2:0] 29 | rubik.f3[2:0] 30 | rubik.r0[2:0] 31 | rubik.r1[2:0] 32 | rubik.r2[2:0] 33 | rubik.r3[2:0] 34 | rubik.d0[2:0] 35 | rubik.d1[2:0] 36 | rubik.d2[2:0] 37 | rubik.d3[2:0] 38 | rubik.b0[2:0] 39 | rubik.b1[2:0] 40 | rubik.b2[2:0] 41 | rubik.b3[2:0] 42 | rubik.l0[2:0] 43 | rubik.l1[2:0] 44 | rubik.l2[2:0] 45 | rubik.l3[2:0] 46 | rubik.f_rst 47 | [pattern_trace] 1 48 | [pattern_trace] 0 49 | -------------------------------------------------------------------------------- /rubik/rubik.psl: -------------------------------------------------------------------------------- 1 | vunit i_rubik(rubik(synthesis)) 2 | { 3 | 4 | signal f_num_colors : integer_vector(0 to 7); 5 | 6 | 7 | -- set all declarations to run on clk_i 8 | default clock is rising_edge(clk_i); 9 | 10 | 11 | ----------------------------- 12 | -- ASSERTIONS ABOUT OUTPUTS 13 | ----------------------------- 14 | 15 | f_edge_u2_f0 : assert always {u2 /= f0}; 16 | f_edge_u2_l1 : assert always {u2 /= l1}; 17 | f_edge_l1_f0 : assert always {l1 /= f0}; 18 | 19 | f_edge_u3_f1 : assert always {u3 /= f1}; 20 | f_edge_r0_f1 : assert always {r0 /= f1}; 21 | f_edge_r0_u3 : assert always {r0 /= u3}; 22 | 23 | f_edge_l3_f2 : assert always {l3 /= f2}; 24 | f_edge_d0_f2 : assert always {d0 /= f2}; 25 | f_edge_l3_d0 : assert always {l3 /= d0}; 26 | 27 | f_edge_d1_f3 : assert always {d1 /= f3}; 28 | f_edge_r2_f3 : assert always {r2 /= f3}; 29 | f_edge_d1_r2 : assert always {d1 /= r2}; 30 | 31 | f_edge_u0_l0 : assert always {u0 /= l0}; 32 | f_edge_b1_l0 : assert always {b1 /= l0}; 33 | f_edge_u0_b1 : assert always {u0 /= b1}; 34 | 35 | f_edge_l2_d2 : assert always {l2 /= d2}; 36 | f_edge_d2_b3 : assert always {d2 /= b3}; 37 | f_edge_b3_l2 : assert always {b3 /= l2}; 38 | 39 | f_edge_d3_r3 : assert always {d3 /= r3}; 40 | f_edge_r3_b2 : assert always {r3 /= b2}; 41 | f_edge_d3_b2 : assert always {d3 /= b2}; 42 | 43 | f_edge_r1_u1 : assert always {r1 /= u1}; 44 | f_edge_r1_b0 : assert always {r1 /= b0}; 45 | f_edge_u1_b0 : assert always {u1 /= b0}; 46 | 47 | process (all) 48 | variable num_colors : integer_vector(0 to 7); 49 | begin 50 | num_colors := (others => 0); 51 | num_colors(to_integer(u0)) := num_colors(to_integer(u0)) + 1; 52 | num_colors(to_integer(u1)) := num_colors(to_integer(u1)) + 1; 53 | num_colors(to_integer(u2)) := num_colors(to_integer(u2)) + 1; 54 | num_colors(to_integer(u3)) := num_colors(to_integer(u3)) + 1; 55 | num_colors(to_integer(f0)) := num_colors(to_integer(f0)) + 1; 56 | num_colors(to_integer(f1)) := num_colors(to_integer(f1)) + 1; 57 | num_colors(to_integer(f2)) := num_colors(to_integer(f2)) + 1; 58 | num_colors(to_integer(f3)) := num_colors(to_integer(f3)) + 1; 59 | num_colors(to_integer(r0)) := num_colors(to_integer(r0)) + 1; 60 | num_colors(to_integer(r1)) := num_colors(to_integer(r1)) + 1; 61 | num_colors(to_integer(r2)) := num_colors(to_integer(r2)) + 1; 62 | num_colors(to_integer(r3)) := num_colors(to_integer(r3)) + 1; 63 | num_colors(to_integer(d0)) := num_colors(to_integer(d0)) + 1; 64 | num_colors(to_integer(d1)) := num_colors(to_integer(d1)) + 1; 65 | num_colors(to_integer(d2)) := num_colors(to_integer(d2)) + 1; 66 | num_colors(to_integer(d3)) := num_colors(to_integer(d3)) + 1; 67 | num_colors(to_integer(b0)) := num_colors(to_integer(b0)) + 1; 68 | num_colors(to_integer(b1)) := num_colors(to_integer(b1)) + 1; 69 | num_colors(to_integer(b2)) := num_colors(to_integer(b2)) + 1; 70 | num_colors(to_integer(b3)) := num_colors(to_integer(b3)) + 1; 71 | num_colors(to_integer(l0)) := num_colors(to_integer(l0)) + 1; 72 | num_colors(to_integer(l1)) := num_colors(to_integer(l1)) + 1; 73 | num_colors(to_integer(l2)) := num_colors(to_integer(l2)) + 1; 74 | num_colors(to_integer(l3)) := num_colors(to_integer(l3)) + 1; 75 | f_num_colors <= num_colors; 76 | end process; 77 | 78 | f_colors : assert always {f_num_colors(0 to 5) = (0 to 5 => 4)}; 79 | 80 | 81 | ----------------------------- 82 | -- ASSUMPTIONS ABOUT INPUTS 83 | ----------------------------- 84 | 85 | -- We prevent any reset at all, because that would be cheating :-) 86 | f_no_rst : assume always {not rst_i}; 87 | 88 | 89 | -------------------------------------------- 90 | -- COVER STATEMENTS TO VERIFY REACHABILITY 91 | -------------------------------------------- 92 | 93 | -- Attempt to solve the cube from the given initial condition. 94 | f_done : cover {done_o}; 95 | 96 | } -- vunit i_rubik(rubik(synthesis)) 97 | 98 | -------------------------------------------------------------------------------- /rubik/rubik.sby: -------------------------------------------------------------------------------- 1 | [tasks] 2 | bmc 3 | cover 4 | 5 | [options] 6 | bmc: mode bmc 7 | bmc: depth 3 8 | cover: mode cover 9 | cover: depth 10 10 | 11 | [engines] 12 | smtbmc 13 | 14 | [script] 15 | ghdl --std=08 rubik.vhd rubik.psl -e rubik 16 | prep -top rubik 17 | 18 | [files] 19 | rubik.vhd 20 | rubik.psl 21 | 22 | -------------------------------------------------------------------------------- /rubik/rubik_sim.gtkw: -------------------------------------------------------------------------------- 1 | [*] 2 | [*] GTKWave Analyzer v3.3.86 (w)1999-2017 BSI 3 | [*] Mon Dec 28 11:27:01 2020 4 | [*] 5 | [dumpfile] "/home/mike/git/MJoergen/formal/rubik/rubik.ghw" 6 | [dumpfile_mtime] "Mon Dec 28 11:24:50 2020" 7 | [dumpfile_size] 47493 8 | [savefile] "/home/mike/git/MJoergen/formal/rubik/rubik_sim.gtkw" 9 | [timestart] 0 10 | [size] 1920 1011 11 | [pos] -1 -1 12 | *-29.143534 20000000 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 13 | [treeopen] top. 14 | [treeopen] top.rubik_tb. 15 | [treeopen] top.rubik_tb.i_rubik. 16 | [sst_width] 249 17 | [signals_width] 275 18 | [sst_expanded] 1 19 | [sst_vpaned_height] 286 20 | @28 21 | top.rubik_tb.i_rubik.clk_i 22 | top.rubik_tb.i_rubik.rst_i 23 | @29 24 | #{top.rubik_tb.i_rubik.cmd_i[3:0]} top.rubik_tb.i_rubik.cmd_i[3] top.rubik_tb.i_rubik.cmd_i[2] top.rubik_tb.i_rubik.cmd_i[1] top.rubik_tb.i_rubik.cmd_i[0] 25 | @28 26 | top.rubik_tb.i_rubik.done_o 27 | #{top.rubik_tb.i_rubik.u0[2:0]} top.rubik_tb.i_rubik.u0[2] top.rubik_tb.i_rubik.u0[1] top.rubik_tb.i_rubik.u0[0] 28 | #{top.rubik_tb.i_rubik.u1[2:0]} top.rubik_tb.i_rubik.u1[2] top.rubik_tb.i_rubik.u1[1] top.rubik_tb.i_rubik.u1[0] 29 | #{top.rubik_tb.i_rubik.u2[2:0]} top.rubik_tb.i_rubik.u2[2] top.rubik_tb.i_rubik.u2[1] top.rubik_tb.i_rubik.u2[0] 30 | #{top.rubik_tb.i_rubik.u3[2:0]} top.rubik_tb.i_rubik.u3[2] top.rubik_tb.i_rubik.u3[1] top.rubik_tb.i_rubik.u3[0] 31 | #{top.rubik_tb.i_rubik.f0[2:0]} top.rubik_tb.i_rubik.f0[2] top.rubik_tb.i_rubik.f0[1] top.rubik_tb.i_rubik.f0[0] 32 | #{top.rubik_tb.i_rubik.f1[2:0]} top.rubik_tb.i_rubik.f1[2] top.rubik_tb.i_rubik.f1[1] top.rubik_tb.i_rubik.f1[0] 33 | #{top.rubik_tb.i_rubik.f2[2:0]} top.rubik_tb.i_rubik.f2[2] top.rubik_tb.i_rubik.f2[1] top.rubik_tb.i_rubik.f2[0] 34 | #{top.rubik_tb.i_rubik.f3[2:0]} top.rubik_tb.i_rubik.f3[2] top.rubik_tb.i_rubik.f3[1] top.rubik_tb.i_rubik.f3[0] 35 | #{top.rubik_tb.i_rubik.r0[2:0]} top.rubik_tb.i_rubik.r0[2] top.rubik_tb.i_rubik.r0[1] top.rubik_tb.i_rubik.r0[0] 36 | #{top.rubik_tb.i_rubik.r1[2:0]} top.rubik_tb.i_rubik.r1[2] top.rubik_tb.i_rubik.r1[1] top.rubik_tb.i_rubik.r1[0] 37 | #{top.rubik_tb.i_rubik.r2[2:0]} top.rubik_tb.i_rubik.r2[2] top.rubik_tb.i_rubik.r2[1] top.rubik_tb.i_rubik.r2[0] 38 | #{top.rubik_tb.i_rubik.r3[2:0]} top.rubik_tb.i_rubik.r3[2] top.rubik_tb.i_rubik.r3[1] top.rubik_tb.i_rubik.r3[0] 39 | #{top.rubik_tb.i_rubik.d0[2:0]} top.rubik_tb.i_rubik.d0[2] top.rubik_tb.i_rubik.d0[1] top.rubik_tb.i_rubik.d0[0] 40 | #{top.rubik_tb.i_rubik.d1[2:0]} top.rubik_tb.i_rubik.d1[2] top.rubik_tb.i_rubik.d1[1] top.rubik_tb.i_rubik.d1[0] 41 | #{top.rubik_tb.i_rubik.d2[2:0]} top.rubik_tb.i_rubik.d2[2] top.rubik_tb.i_rubik.d2[1] top.rubik_tb.i_rubik.d2[0] 42 | #{top.rubik_tb.i_rubik.d3[2:0]} top.rubik_tb.i_rubik.d3[2] top.rubik_tb.i_rubik.d3[1] top.rubik_tb.i_rubik.d3[0] 43 | #{top.rubik_tb.i_rubik.b0[2:0]} top.rubik_tb.i_rubik.b0[2] top.rubik_tb.i_rubik.b0[1] top.rubik_tb.i_rubik.b0[0] 44 | #{top.rubik_tb.i_rubik.b1[2:0]} top.rubik_tb.i_rubik.b1[2] top.rubik_tb.i_rubik.b1[1] top.rubik_tb.i_rubik.b1[0] 45 | #{top.rubik_tb.i_rubik.b2[2:0]} top.rubik_tb.i_rubik.b2[2] top.rubik_tb.i_rubik.b2[1] top.rubik_tb.i_rubik.b2[0] 46 | #{top.rubik_tb.i_rubik.b3[2:0]} top.rubik_tb.i_rubik.b3[2] top.rubik_tb.i_rubik.b3[1] top.rubik_tb.i_rubik.b3[0] 47 | #{top.rubik_tb.i_rubik.l0[2:0]} top.rubik_tb.i_rubik.l0[2] top.rubik_tb.i_rubik.l0[1] top.rubik_tb.i_rubik.l0[0] 48 | #{top.rubik_tb.i_rubik.l1[2:0]} top.rubik_tb.i_rubik.l1[2] top.rubik_tb.i_rubik.l1[1] top.rubik_tb.i_rubik.l1[0] 49 | #{top.rubik_tb.i_rubik.l2[2:0]} top.rubik_tb.i_rubik.l2[2] top.rubik_tb.i_rubik.l2[1] top.rubik_tb.i_rubik.l2[0] 50 | #{top.rubik_tb.i_rubik.l3[2:0]} top.rubik_tb.i_rubik.l3[2] top.rubik_tb.i_rubik.l3[1] top.rubik_tb.i_rubik.l3[0] 51 | top.rubik_tb.i_rubik.f_rst 52 | @22 53 | #{top.rubik_tb.i_rubik.f_num_colors[0:7]} top.rubik_tb.i_rubik.f_num_colors[0] top.rubik_tb.i_rubik.f_num_colors[1] top.rubik_tb.i_rubik.f_num_colors[2] top.rubik_tb.i_rubik.f_num_colors[3] top.rubik_tb.i_rubik.f_num_colors[4] top.rubik_tb.i_rubik.f_num_colors[5] top.rubik_tb.i_rubik.f_num_colors[6] top.rubik_tb.i_rubik.f_num_colors[7] 54 | [pattern_trace] 1 55 | [pattern_trace] 0 56 | -------------------------------------------------------------------------------- /rubik/rubik_tb.vhd: -------------------------------------------------------------------------------- 1 | library ieee; 2 | use ieee.std_logic_1164.all; 3 | use ieee.numeric_std.all; 4 | 5 | entity rubik_tb is 6 | end entity rubik_tb; 7 | 8 | architecture simulation of rubik_tb is 9 | 10 | signal clk : std_logic; 11 | signal rst : std_logic; 12 | signal cmd : std_logic_vector(3 downto 0); 13 | signal done : std_logic; 14 | 15 | constant C_CMD_FP : std_logic_vector(3 downto 0) := "0101"; 16 | constant C_CMD_F2 : std_logic_vector(3 downto 0) := "0110"; 17 | constant C_CMD_FM : std_logic_vector(3 downto 0) := "0111"; 18 | 19 | constant C_CMD_RP : std_logic_vector(3 downto 0) := "1001"; 20 | constant C_CMD_R2 : std_logic_vector(3 downto 0) := "1010"; 21 | constant C_CMD_RM : std_logic_vector(3 downto 0) := "1011"; 22 | 23 | constant C_CMD_UP : std_logic_vector(3 downto 0) := "1101"; 24 | constant C_CMD_U2 : std_logic_vector(3 downto 0) := "1110"; 25 | constant C_CMD_UM : std_logic_vector(3 downto 0) := "1111"; 26 | 27 | -- Random number generator with initial seed 28 | signal prbs255 : std_logic_vector(254 downto 0) := (others => '1'); 29 | 30 | begin 31 | 32 | p_clk : process 33 | begin 34 | clk <= '1', '0' after 5 ns; 35 | wait for 10 ns; 36 | end process p_clk; 37 | 38 | p_rst : process 39 | begin 40 | rst <= '1'; 41 | wait until clk = '1'; 42 | wait until clk = '1'; 43 | wait until clk = '1'; 44 | rst <= '0'; 45 | wait until clk = '1'; 46 | wait; 47 | end process p_rst; 48 | 49 | -------------------------------------------- 50 | -- Random number generator, based on a PRBS 51 | -------------------------------------------- 52 | 53 | p_prbs255 : process (clk) 54 | begin 55 | if rising_edge(clk) then 56 | prbs255 <= prbs255(253 downto 0) 57 | & (prbs255(254) xor prbs255(13) xor prbs255(17) xor prbs255(126)); 58 | end if; 59 | end process p_prbs255; 60 | 61 | 62 | p_test : process 63 | procedure repeat(cmd_p : std_logic_vector(3 downto 0); count_p : integer) is 64 | begin 65 | for i in 1 to count_p loop 66 | cmd <= cmd_p; 67 | wait until clk = '1'; 68 | cmd <= "0000"; 69 | wait until clk = '1'; 70 | wait until clk = '1'; 71 | wait until clk = '1'; 72 | end loop; 73 | end procedure repeat; 74 | 75 | begin 76 | cmd <= "0000"; 77 | wait until rst = '0'; 78 | wait until clk = '1'; 79 | assert done = '1'; 80 | 81 | -- Test period of each rotation. 82 | -- I.e. verify that after repeating the rotation we get back the original cube. 83 | repeat(C_CMD_FP, 4); assert done = '1'; 84 | repeat("0000", 1); 85 | repeat(C_CMD_F2, 2); assert done = '1'; 86 | repeat("0000", 1); 87 | repeat(C_CMD_FM, 4); assert done = '1'; 88 | repeat("0000", 2); 89 | 90 | repeat(C_CMD_RP, 4); assert done = '1'; 91 | repeat("0000", 1); 92 | repeat(C_CMD_R2, 2); assert done = '1'; 93 | repeat("0000", 1); 94 | repeat(C_CMD_RM, 4); assert done = '1'; 95 | repeat("0000", 2); 96 | 97 | repeat(C_CMD_UP, 4); assert done = '1'; 98 | repeat("0000", 1); 99 | repeat(C_CMD_U2, 2); assert done = '1'; 100 | repeat("0000", 1); 101 | repeat(C_CMD_UM, 4); assert done = '1'; 102 | repeat("0000", 2); 103 | 104 | -- Generate some random rotations. 105 | -- Any illegal commands will just be skipped. 106 | for i in 1 to 50 loop 107 | repeat(prbs255(3 downto 0), 1); 108 | end loop; 109 | 110 | wait; 111 | end process p_test; 112 | 113 | i_rubik : entity work.rubik 114 | port map ( 115 | clk_i => clk, 116 | rst_i => rst, 117 | cmd_i => cmd, 118 | done_o => done 119 | ); -- i_rubik 120 | 121 | end architecture simulation; 122 | 123 | -------------------------------------------------------------------------------- /rubik/waveform.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MJoergen/formal/9e0bc6c20361925199b3a94eb3dd362aba29b010/rubik/waveform.png -------------------------------------------------------------------------------- /rubik2/Makefile: -------------------------------------------------------------------------------- 1 | DUT = rubik 2 | 3 | # This is the main command line to run the formal verification 4 | all: 5 | sby --yosys "yosys -m ghdl" -f $(DUT).sby 6 | 7 | show_bmc: 8 | gtkwave $(DUT)_bmc/engine_0/trace.vcd $(DUT).gtkw 9 | 10 | show_cover: 11 | gtkwave $(DUT)_cover/engine_0/trace0.vcd $(DUT).gtkw 12 | 13 | show_prove: 14 | gtkwave $(DUT)_prove/engine_0/trace_induct.vcd $(DUT).gtkw 15 | 16 | clean: 17 | rm -rf $(DUT)_bmc/ 18 | rm -rf $(DUT)_cover/ 19 | rm -rf $(DUT)_prove/ 20 | rm -rf work-obj08.cf 21 | rm -rf yosys.log 22 | rm -rf $(DUT).edif 23 | rm -rf $(DUT).ghw 24 | 25 | # Synthesis 26 | 27 | work-obj08.cf: $(DUT).vhd 28 | ghdl -a -fpsl -fsynopsys --std=08 $^ 29 | 30 | synth: work-obj08.cf 31 | yosys -m ghdl -p 'ghdl -fpsl -fsynopsys --std=08 $(DUT); synth_xilinx -top $(DUT) -edif $(DUT).edif' > yosys.log 32 | 33 | # Simulation 34 | 35 | sim: $(DUT).vhd $(DUT)_tb.vhd 36 | ghdl -i -fpsl --std=08 --work=work $^ 37 | ghdl -m -fpsl --std=08 --ieee=synopsys -fexplicit $(DUT)_tb 38 | ghdl -r -fpsl --std=08 --ieee=synopsys $(DUT)_tb --wave=$(DUT).ghw --stop-time=4us 39 | gtkwave $(DUT).ghw $(DUT)_sim.gtkw 40 | 41 | -------------------------------------------------------------------------------- /rubik2/README.md: -------------------------------------------------------------------------------- 1 | # Rubik's 2x2x2 cube solver 2 | 3 | This is a second version of the Rubik's 2x2x2 cube solver. 4 | 5 | ## Implementation 6 | This time I'm trying to simplify the implementation and instead of considering 7 | individual tiles (24 in total), I consider the position and orientation of each 8 | corner (8 in total). 9 | 10 | I've chosen to keep the action on the cube simple. Since the cube is only 11 | 2x2x2, I can limit the possible rotations to only the `U`, `F`, and `R` sides. 12 | So a total of nine possible commands: 13 | 14 | * `C_CMD_FP = F+` 15 | * `C_CMD_F2 = F2` 16 | * `C_CMD_FM = F-` 17 | * `C_CMD_RP = R+` 18 | * `C_CMD_R2 = R2` 19 | * `C_CMD_RM = R-` 20 | * `C_CMD_UP = U+` 21 | * `C_CMD_U2 = U2` 22 | * `C_CMD_UM = U-` 23 | 24 | The interface to [the module](rubik.vhd) is very simple: 25 | 26 | ``` 27 | cmd_i : in std_logic_vector(3 downto 0); 28 | done_o : out std_logic 29 | ``` 30 | 31 | The combinatorial signal `done_o` is asserted when the cube is solved. 32 | 33 | ## Simulation (manual testbench) 34 | For the sake of generating a suitable problem for the solver, I've implemented 35 | a [regular testbench](rubik_tb.vhd). 36 | 37 | This testbench has two purposes. First it verifies the period of each of the 38 | nine possible rotations. So for instance, the rotation `F+` has a period of 4. 39 | This means that repeating the rotation four times should leave cube unchanged. 40 | This is a manual test (i.e not using formal verification) to clear out some of 41 | the typing mistakes in the implementation. 42 | 43 | The second part of the testbench generates a sequence of random commands, 44 | starting from the solved cube. I then manually record the outcome and use it 45 | as initial condition in the [implementation file](rubik.vhd). 46 | 47 | To run the simulation just type 48 | ``` 49 | make sim 50 | ``` 51 | 52 | ## Formal verification 53 | I'm using formal verification here to try to solve the cube. So from the 54 | testbench I have created a random (but legal!) coloring of the cube, and use 55 | that as initial condition when declaring the signals `corner_ubl`, etc. 56 | 57 | I've added an assertion to verify that the position is indeed legal and can be 58 | solved. This is done by calculating the parity (an invariant) of the position 59 | and asserting it is always zero: 60 | 61 | ``` 62 | f_colors : assert always {f_parity = 0}; 63 | ``` 64 | 65 | For solving the cube I simply add the line: 66 | ``` 67 | f_done : cover {done_o}; 68 | ``` 69 | 70 | However, the solver will try to cheat(!) by asserting the reset signal, so this 71 | must be prevented by: 72 | ``` 73 | f_no_rst : assume always {not rst_i}; 74 | ``` 75 | 76 | Now I can simply run 77 | ``` 78 | make 79 | ``` 80 | 81 | The solution takes around a minute to find, and can then be viewed by writing 82 | ``` 83 | make show_cover 84 | ``` 85 | ![Waveform](waveform.png) 86 | 87 | This shows that the cube (from this particular initial condition) can be solved 88 | in a sequence of nine rotations. 89 | 90 | ## Swapping two corners 91 | In the case where the two top corners (`UFL` and `UFR`) are swapped (but not twisted) 92 | the minimal solution is 10 moves. 93 | 94 | We have the initial condition 95 | ``` 96 | corner_ubl <= "000001"; 97 | corner_ubr <= "001001"; 98 | corner_ufl <= "011001"; -- swapped 99 | corner_ufr <= "010001"; -- swapped 100 | corner_dbl <= "100001"; 101 | corner_dbr <= "101001"; 102 | corner_dfl <= "110001"; 103 | corner_dfr <= "111001"; 104 | ``` 105 | and the solution is 106 | ``` 107 | 1001 108 | 1111 109 | 1001 110 | 1101 111 | 1010 112 | 0110 113 | 1001 114 | 0101 115 | 1011 116 | 0110 117 | ``` 118 | i.e. the sequence `R+`, `U-`, `R+`, `U+`, `R2`, `F2`, `R+`, `F+`, `R-`, `F2`, 119 | as shown in this waveform: 120 | 121 | ![Waveform](swapped.png) 122 | 123 | ## Twisting two corners 124 | In the case where the two top corners (`UFL` and `UFR`) are twisted (but not swapped) 125 | the minimal solution is again 10 moves. 126 | 127 | We have the initial condition 128 | ``` 129 | corner_ubl <= "000001"; 130 | corner_ubr <= "001001"; 131 | corner_ufl <= "010010"; -- twisted 132 | corner_ufr <= "011100"; -- twisted 133 | corner_dbl <= "100001"; 134 | corner_dbr <= "101001"; 135 | corner_dfl <= "110001"; 136 | corner_dfr <= "111001"; 137 | ``` 138 | and the solution is 139 | ``` 140 | 1101 141 | 0111 142 | 1101 143 | 1011 144 | 1101 145 | 1011 146 | 0111 147 | 1010 148 | 1110 149 | 0101 150 | ``` 151 | i.e. the sequence `U+`, `F-`, `U+`, `R-`, `U+`, `R-`, `F-`, `R2`, `U2`, `F+` 152 | as shown in this waveform: 153 | 154 | ![Waveform](twisted.png) 155 | 156 | 157 | 158 | ## Synthesis 159 | Finally, just for fun, we can synthesize the module by typing 160 | ``` 161 | make synth 162 | ``` 163 | The result can be seen below: 164 | ``` 165 | Number of cells: 182 166 | BUFG 1 167 | FDRE 24 168 | FDSE 18 169 | IBUF 6 170 | LUT2 1 171 | LUT3 10 172 | LUT4 1 173 | LUT5 45 174 | LUT6 51 175 | MUXF7 20 176 | MUXF8 4 177 | OBUF 1 178 | 179 | Estimated number of LCs: 107 180 | ``` 181 | 182 | So now we're using 42 registers, that is 6 registers for 7 corners, instead of 63 registers in the previous implementation. 183 | 184 | -------------------------------------------------------------------------------- /rubik2/rubik.gtkw: -------------------------------------------------------------------------------- 1 | [*] 2 | [*] GTKWave Analyzer v3.3.103 (w)1999-2019 BSI 3 | [*] Sun Jan 10 15:38:04 2021 4 | [*] 5 | [dumpfile] "/home/mike/git/MJoergen/formal/rubik2/rubik_cover/engine_0/trace0.vcd" 6 | [dumpfile_mtime] "Sun Jan 10 15:31:30 2021" 7 | [dumpfile_size] 2285 8 | [savefile] "/home/mike/git/MJoergen/formal/rubik2/rubik.gtkw" 9 | [timestart] 0 10 | [size] 1920 1011 11 | [pos] -1 -1 12 | *-4.045955 96 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 13 | [sst_width] 249 14 | [signals_width] 280 15 | [sst_expanded] 1 16 | [sst_vpaned_height] 286 17 | @28 18 | rubik.clk_i 19 | rubik.rst_i 20 | rubik.cmd_i[3:0] 21 | rubik.done_o 22 | @200 23 | - 24 | @28 25 | rubik.corner_ubl[5:0] 26 | rubik.corner_ubr[5:0] 27 | rubik.corner_ufl[5:0] 28 | rubik.corner_ufr[5:0] 29 | rubik.corner_dbl[5:0] 30 | rubik.corner_dbr[5:0] 31 | rubik.corner_dfl[5:0] 32 | rubik.corner_dfr[5:0] 33 | [pattern_trace] 1 34 | [pattern_trace] 0 35 | -------------------------------------------------------------------------------- /rubik2/rubik.psl: -------------------------------------------------------------------------------- 1 | vunit i_rubik(rubik(synthesis)) 2 | { 3 | 4 | signal f_parity : integer range 0 to 2; 5 | 6 | 7 | -- set all declarations to run on clk_i 8 | default clock is rising_edge(clk_i); 9 | 10 | 11 | ------------------------------------ 12 | -- ASSERTIONS ABOUT INTERNAL STATE 13 | ------------------------------------ 14 | 15 | process (all) 16 | variable parity : integer; 17 | begin 18 | parity := to_integer(corner_ubl(2 downto 1)); 19 | parity := parity + to_integer(corner_ubr(2 downto 1)); 20 | parity := parity + to_integer(corner_ufl(2 downto 1)); 21 | parity := parity + to_integer(corner_ufr(2 downto 1)); 22 | parity := parity + to_integer(corner_dbl(2 downto 1)); 23 | parity := parity + to_integer(corner_dbr(2 downto 1)); 24 | parity := parity + to_integer(corner_dfl(2 downto 1)); 25 | parity := parity + to_integer(corner_dfr(2 downto 1)); 26 | f_parity <= parity mod 3; 27 | end process; 28 | 29 | f_colors : assert always {f_parity = 0}; 30 | 31 | assert always or(corner_ubl(2 downto 0)) = '1'; 32 | assert always or(corner_ubr(2 downto 0)) = '1'; 33 | assert always or(corner_ufl(2 downto 0)) = '1'; 34 | assert always or(corner_ufr(2 downto 0)) = '1'; 35 | assert always or(corner_dbl(2 downto 0)) = '1'; 36 | assert always or(corner_dbr(2 downto 0)) = '1'; 37 | assert always or(corner_dfl(2 downto 0)) = '1'; 38 | assert always or(corner_dfr(2 downto 0)) = '1'; 39 | 40 | assert always and(corner_ubl(2 downto 1)) = '0'; 41 | assert always and(corner_ubr(2 downto 1)) = '0'; 42 | assert always and(corner_ufl(2 downto 1)) = '0'; 43 | assert always and(corner_ufr(2 downto 1)) = '0'; 44 | assert always and(corner_dbl(2 downto 1)) = '0'; 45 | assert always and(corner_dbr(2 downto 1)) = '0'; 46 | assert always and(corner_dfl(2 downto 1)) = '0'; 47 | assert always and(corner_dfr(2 downto 1)) = '0'; 48 | 49 | assert always nor(corner_ubl(2 downto 1)) = corner_ubl(0); 50 | assert always nor(corner_ubr(2 downto 1)) = corner_ubr(0); 51 | assert always nor(corner_ufl(2 downto 1)) = corner_ufl(0); 52 | assert always nor(corner_ufr(2 downto 1)) = corner_ufr(0); 53 | assert always nor(corner_dbl(2 downto 1)) = corner_dbl(0); 54 | assert always nor(corner_dbr(2 downto 1)) = corner_dbr(0); 55 | assert always nor(corner_dfl(2 downto 1)) = corner_dfl(0); 56 | assert always nor(corner_dfr(2 downto 1)) = corner_dfr(0); 57 | 58 | 59 | ----------------------------- 60 | -- ASSUMPTIONS ABOUT INPUTS 61 | ----------------------------- 62 | 63 | -- We prevent any reset at all, because that would be cheating :-) 64 | f_no_rst : assume always {not rst_i}; 65 | 66 | 67 | -------------------------------------------- 68 | -- COVER STATEMENTS TO VERIFY REACHABILITY 69 | -------------------------------------------- 70 | 71 | -- Attempt to solve the cube from the given initial condition. 72 | f_done : cover {done_o}; 73 | 74 | } -- vunit i_rubik(rubik(synthesis)) 75 | 76 | -------------------------------------------------------------------------------- /rubik2/rubik.sby: -------------------------------------------------------------------------------- 1 | [tasks] 2 | bmc 3 | prove 4 | cover 5 | 6 | [options] 7 | bmc: mode bmc 8 | bmc: depth 5 9 | prove: mode prove 10 | prove: depth 4 11 | cover: mode cover 12 | cover: depth 11 13 | 14 | [engines] 15 | smtbmc 16 | 17 | [script] 18 | ghdl --std=08 rubik.vhd rubik.psl -e rubik 19 | prep -top rubik 20 | 21 | [files] 22 | rubik.vhd 23 | rubik.psl 24 | 25 | -------------------------------------------------------------------------------- /rubik2/rubik.vhd: -------------------------------------------------------------------------------- 1 | library ieee; 2 | use ieee.std_logic_1164.all; 3 | use ieee.numeric_std_unsigned.all; 4 | 5 | -- This attempts to solve the 2x2x2 Rubik's cube using formal methods. 6 | -- 7 | -- The cube is described by the position and orientation of the 8 | -- eight corners. 9 | -- The six faces are named Left, Front, Right, Back, Up, and Down. 10 | -- The eight corners are hence named UBL, UBR, UFL, UFR, DBL, DBR, 11 | -- DFL, and DFR. 12 | -- 13 | -- The input signal cmd_i is interpreted as follows 14 | -- 0101 : F+ 15 | -- 0110 : F2 16 | -- 0111 : F- 17 | -- 1001 : R+ 18 | -- 1010 : R2 19 | -- 1011 : R- 20 | -- 1101 : U+ 21 | -- 1110 : U2 22 | -- 1111 : U- 23 | -- Only these combinations of input pins are allowed 24 | -- 25 | -- The output signal done_o is true when the cube is solved. 26 | 27 | entity rubik is 28 | port ( 29 | clk_i : in std_logic; 30 | rst_i : in std_logic; 31 | cmd_i : in std_logic_vector(3 downto 0); 32 | done_o : out std_logic 33 | ); 34 | end entity rubik; 35 | 36 | architecture synthesis of rubik is 37 | 38 | -- Valid input commands 39 | constant C_CMD_FP : std_logic_vector(3 downto 0) := "0101"; -- F+ 40 | constant C_CMD_F2 : std_logic_vector(3 downto 0) := "0110"; -- F2 41 | constant C_CMD_FM : std_logic_vector(3 downto 0) := "0111"; -- F- 42 | 43 | constant C_CMD_RP : std_logic_vector(3 downto 0) := "1001"; -- R+ 44 | constant C_CMD_R2 : std_logic_vector(3 downto 0) := "1010"; -- R2 45 | constant C_CMD_RM : std_logic_vector(3 downto 0) := "1011"; -- R- 46 | 47 | constant C_CMD_UP : std_logic_vector(3 downto 0) := "1101"; -- U+ 48 | constant C_CMD_U2 : std_logic_vector(3 downto 0) := "1110"; -- U2 49 | constant C_CMD_UM : std_logic_vector(3 downto 0) := "1111"; -- U- 50 | 51 | -- The initial condition is generated from the simulation testbench 52 | -- Bits 5-3 denote the identity of the corner piece, whereas 53 | -- bits 2-0 denote the orientation. 54 | signal corner_ubl : std_logic_vector(5 downto 0) := "110100"; 55 | signal corner_ubr : std_logic_vector(5 downto 0) := "000010"; 56 | signal corner_ufl : std_logic_vector(5 downto 0) := "001001"; 57 | signal corner_ufr : std_logic_vector(5 downto 0) := "011010"; 58 | signal corner_dbl : std_logic_vector(5 downto 0) := "100001"; 59 | signal corner_dbr : std_logic_vector(5 downto 0) := "101010"; 60 | signal corner_dfl : std_logic_vector(5 downto 0) := "010001"; 61 | signal corner_dfr : std_logic_vector(5 downto 0) := "111010"; 62 | 63 | -- -- Swapped corners 64 | -- signal corner_ubl : std_logic_vector(5 downto 0) := "000001"; 65 | -- signal corner_ubr : std_logic_vector(5 downto 0) := "001001"; 66 | -- signal corner_ufl : std_logic_vector(5 downto 0) := "011001"; -- swapped 67 | -- signal corner_ufr : std_logic_vector(5 downto 0) := "010001"; -- swapped 68 | -- signal corner_dbl : std_logic_vector(5 downto 0) := "100001"; 69 | -- signal corner_dbr : std_logic_vector(5 downto 0) := "101001"; 70 | -- signal corner_dfl : std_logic_vector(5 downto 0) := "110001"; 71 | -- signal corner_dfr : std_logic_vector(5 downto 0) := "111001"; 72 | 73 | -- -- Twisted corners 74 | -- signal corner_ubl : std_logic_vector(5 downto 0) := "000001"; 75 | -- signal corner_ubr : std_logic_vector(5 downto 0) := "001001"; 76 | -- signal corner_ufl : std_logic_vector(5 downto 0) := "010010"; -- twisted 77 | -- signal corner_ufr : std_logic_vector(5 downto 0) := "011100"; -- twisted 78 | -- signal corner_dbl : std_logic_vector(5 downto 0) := "100001"; 79 | -- signal corner_dbr : std_logic_vector(5 downto 0) := "101001"; 80 | -- signal corner_dfl : std_logic_vector(5 downto 0) := "110001"; 81 | -- signal corner_dfr : std_logic_vector(5 downto 0) := "111001"; 82 | 83 | begin 84 | 85 | process (clk_i) 86 | 87 | -- Rotate a corner piece counter clockwise 88 | function left(arg : std_logic_vector) return std_logic_vector is 89 | begin 90 | return arg(5 downto 3) & arg(0) & arg(2) & arg(1); 91 | end function left; 92 | 93 | -- Rotate a corner piece clockwise 94 | function right(arg : std_logic_vector) return std_logic_vector is 95 | begin 96 | return arg(5 downto 3) & arg(1) & arg(0) & arg(2); 97 | end function right; 98 | 99 | begin 100 | if rising_edge(clk_i) then 101 | case cmd_i is 102 | 103 | when C_CMD_FP => -- : F+ 104 | corner_ufr <= right(corner_ufl); 105 | corner_ufl <= left(corner_dfl); 106 | corner_dfl <= right(corner_dfr); 107 | corner_dfr <= left(corner_ufr); 108 | 109 | when C_CMD_F2 => -- : F2 110 | corner_ufl <= corner_dfr; 111 | corner_dfr <= corner_ufl; 112 | corner_ufr <= corner_dfl; 113 | corner_dfl <= corner_ufr; 114 | 115 | when C_CMD_FM => -- : F- 116 | corner_ufl <= left(corner_ufr); 117 | corner_ufr <= right(corner_dfr); 118 | corner_dfr <= left(corner_dfl); 119 | corner_dfl <= right(corner_ufl); 120 | 121 | when C_CMD_RP => -- : R+ 122 | corner_ubr <= right(corner_ufr); 123 | corner_ufr <= left(corner_dfr); 124 | corner_dfr <= right(corner_dbr); 125 | corner_dbr <= left(corner_ubr); 126 | 127 | when C_CMD_R2 => -- : R2 128 | corner_ufr <= corner_dbr; 129 | corner_dbr <= corner_ufr; 130 | corner_ubr <= corner_dfr; 131 | corner_dfr <= corner_ubr; 132 | 133 | when C_CMD_RM => -- : R- 134 | corner_ufr <= left(corner_ubr); 135 | corner_ubr <= right(corner_dbr); 136 | corner_dbr <= left(corner_dfr); 137 | corner_dfr <= right(corner_ufr); 138 | 139 | when C_CMD_UP => -- : U+ 140 | corner_ubr <= corner_ubl; 141 | corner_ubl <= corner_ufl; 142 | corner_ufl <= corner_ufr; 143 | corner_ufr <= corner_ubr; 144 | 145 | when C_CMD_U2 => -- : U2 146 | corner_ubl <= corner_ufr; 147 | corner_ufr <= corner_ubl; 148 | corner_ubr <= corner_ufl; 149 | corner_ufl <= corner_ubr; 150 | 151 | when C_CMD_UM => -- : U- 152 | corner_ubl <= corner_ubr; 153 | corner_ubr <= corner_ufr; 154 | corner_ufr <= corner_ufl; 155 | corner_ufl <= corner_ubl; 156 | 157 | when others => null; -- Ignore any illegal commands 158 | end case; 159 | 160 | -- Reset the cube to the solved position. 161 | if rst_i = '1' then 162 | corner_ubl <= "000001"; 163 | corner_ubr <= "001001"; 164 | corner_ufl <= "010001"; 165 | corner_ufr <= "011001"; 166 | corner_dbl <= "100001"; 167 | corner_dbr <= "101001"; 168 | corner_dfl <= "110001"; 169 | corner_dfr <= "111001"; 170 | end if; 171 | end if; 172 | end process; 173 | 174 | -- Check whether the cube is in the solved position. 175 | done_o <= '1' when 176 | corner_ubl = "000001" and 177 | corner_ubr = "001001" and 178 | corner_ufl = "010001" and 179 | corner_ufr = "011001" and 180 | corner_dbl = "100001" and 181 | corner_dbr = "101001" and 182 | corner_dfl = "110001" and 183 | corner_dfr = "111001" else '0'; 184 | 185 | end architecture synthesis; 186 | 187 | -------------------------------------------------------------------------------- /rubik2/rubik_sim.gtkw: -------------------------------------------------------------------------------- 1 | [*] 2 | [*] GTKWave Analyzer v3.3.103 (w)1999-2019 BSI 3 | [*] Sun Jan 10 15:10:53 2021 4 | [*] 5 | [dumpfile] "/home/mike/git/MJoergen/formal/rubik2/rubik.ghw" 6 | [dumpfile_mtime] "Sun Jan 10 15:10:03 2021" 7 | [dumpfile_size] 46771 8 | [savefile] "/home/mike/git/MJoergen/formal/rubik2/rubik_sim.gtkw" 9 | [timestart] 0 10 | [size] 1920 1011 11 | [pos] -1672 29 12 | *-29.143534 3842000000 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 13 | [treeopen] top. 14 | [treeopen] top.rubik_tb. 15 | [treeopen] top.rubik_tb.i_rubik. 16 | [sst_width] 249 17 | [signals_width] 275 18 | [sst_expanded] 1 19 | [sst_vpaned_height] 286 20 | @28 21 | top.rubik_tb.i_rubik.clk_i 22 | top.rubik_tb.i_rubik.rst_i 23 | #{top.rubik_tb.i_rubik.cmd_i[3:0]} top.rubik_tb.i_rubik.cmd_i[3] top.rubik_tb.i_rubik.cmd_i[2] top.rubik_tb.i_rubik.cmd_i[1] top.rubik_tb.i_rubik.cmd_i[0] 24 | top.rubik_tb.i_rubik.done_o 25 | @200 26 | - 27 | @28 28 | #{top.rubik_tb.i_rubik.corner_ubl[5:0]} top.rubik_tb.i_rubik.corner_ubl[5] top.rubik_tb.i_rubik.corner_ubl[4] top.rubik_tb.i_rubik.corner_ubl[3] top.rubik_tb.i_rubik.corner_ubl[2] top.rubik_tb.i_rubik.corner_ubl[1] top.rubik_tb.i_rubik.corner_ubl[0] 29 | #{top.rubik_tb.i_rubik.corner_ubr[5:0]} top.rubik_tb.i_rubik.corner_ubr[5] top.rubik_tb.i_rubik.corner_ubr[4] top.rubik_tb.i_rubik.corner_ubr[3] top.rubik_tb.i_rubik.corner_ubr[2] top.rubik_tb.i_rubik.corner_ubr[1] top.rubik_tb.i_rubik.corner_ubr[0] 30 | #{top.rubik_tb.i_rubik.corner_ufl[5:0]} top.rubik_tb.i_rubik.corner_ufl[5] top.rubik_tb.i_rubik.corner_ufl[4] top.rubik_tb.i_rubik.corner_ufl[3] top.rubik_tb.i_rubik.corner_ufl[2] top.rubik_tb.i_rubik.corner_ufl[1] top.rubik_tb.i_rubik.corner_ufl[0] 31 | #{top.rubik_tb.i_rubik.corner_ufr[5:0]} top.rubik_tb.i_rubik.corner_ufr[5] top.rubik_tb.i_rubik.corner_ufr[4] top.rubik_tb.i_rubik.corner_ufr[3] top.rubik_tb.i_rubik.corner_ufr[2] top.rubik_tb.i_rubik.corner_ufr[1] top.rubik_tb.i_rubik.corner_ufr[0] 32 | #{top.rubik_tb.i_rubik.corner_dbl[5:0]} top.rubik_tb.i_rubik.corner_dbl[5] top.rubik_tb.i_rubik.corner_dbl[4] top.rubik_tb.i_rubik.corner_dbl[3] top.rubik_tb.i_rubik.corner_dbl[2] top.rubik_tb.i_rubik.corner_dbl[1] top.rubik_tb.i_rubik.corner_dbl[0] 33 | #{top.rubik_tb.i_rubik.corner_dbr[5:0]} top.rubik_tb.i_rubik.corner_dbr[5] top.rubik_tb.i_rubik.corner_dbr[4] top.rubik_tb.i_rubik.corner_dbr[3] top.rubik_tb.i_rubik.corner_dbr[2] top.rubik_tb.i_rubik.corner_dbr[1] top.rubik_tb.i_rubik.corner_dbr[0] 34 | #{top.rubik_tb.i_rubik.corner_dfl[5:0]} top.rubik_tb.i_rubik.corner_dfl[5] top.rubik_tb.i_rubik.corner_dfl[4] top.rubik_tb.i_rubik.corner_dfl[3] top.rubik_tb.i_rubik.corner_dfl[2] top.rubik_tb.i_rubik.corner_dfl[1] top.rubik_tb.i_rubik.corner_dfl[0] 35 | #{top.rubik_tb.i_rubik.corner_dfr[5:0]} top.rubik_tb.i_rubik.corner_dfr[5] top.rubik_tb.i_rubik.corner_dfr[4] top.rubik_tb.i_rubik.corner_dfr[3] top.rubik_tb.i_rubik.corner_dfr[2] top.rubik_tb.i_rubik.corner_dfr[1] top.rubik_tb.i_rubik.corner_dfr[0] 36 | [pattern_trace] 1 37 | [pattern_trace] 0 38 | -------------------------------------------------------------------------------- /rubik2/rubik_tb.vhd: -------------------------------------------------------------------------------- 1 | library ieee; 2 | use ieee.std_logic_1164.all; 3 | use ieee.numeric_std.all; 4 | 5 | entity rubik_tb is 6 | end entity rubik_tb; 7 | 8 | architecture simulation of rubik_tb is 9 | 10 | signal clk : std_logic; 11 | signal rst : std_logic; 12 | signal cmd : std_logic_vector(3 downto 0); 13 | signal done : std_logic; 14 | 15 | constant C_CMD_FP : std_logic_vector(3 downto 0) := "0101"; 16 | constant C_CMD_F2 : std_logic_vector(3 downto 0) := "0110"; 17 | constant C_CMD_FM : std_logic_vector(3 downto 0) := "0111"; 18 | 19 | constant C_CMD_RP : std_logic_vector(3 downto 0) := "1001"; 20 | constant C_CMD_R2 : std_logic_vector(3 downto 0) := "1010"; 21 | constant C_CMD_RM : std_logic_vector(3 downto 0) := "1011"; 22 | 23 | constant C_CMD_UP : std_logic_vector(3 downto 0) := "1101"; 24 | constant C_CMD_U2 : std_logic_vector(3 downto 0) := "1110"; 25 | constant C_CMD_UM : std_logic_vector(3 downto 0) := "1111"; 26 | 27 | -- Random number generator with initial seed 28 | signal prbs255 : std_logic_vector(254 downto 0) := (others => '1'); 29 | 30 | begin 31 | 32 | p_clk : process 33 | begin 34 | clk <= '1', '0' after 5 ns; 35 | wait for 10 ns; 36 | end process p_clk; 37 | 38 | p_rst : process 39 | begin 40 | rst <= '1'; 41 | wait until clk = '1'; 42 | wait until clk = '1'; 43 | wait until clk = '1'; 44 | rst <= '0'; 45 | wait until clk = '1'; 46 | wait; 47 | end process p_rst; 48 | 49 | -------------------------------------------- 50 | -- Random number generator, based on a PRBS 51 | -------------------------------------------- 52 | 53 | p_prbs255 : process (clk) 54 | begin 55 | if rising_edge(clk) then 56 | prbs255 <= prbs255(253 downto 0) 57 | & (prbs255(254) xor prbs255(13) xor prbs255(17) xor prbs255(126)); 58 | end if; 59 | end process p_prbs255; 60 | 61 | 62 | p_test : process 63 | procedure repeat(cmd_p : std_logic_vector(3 downto 0); count_p : integer) is 64 | begin 65 | for i in 1 to count_p loop 66 | cmd <= cmd_p; 67 | wait until clk = '1'; 68 | cmd <= "0000"; 69 | wait until clk = '1'; 70 | wait until clk = '1'; 71 | wait until clk = '1'; 72 | end loop; 73 | end procedure repeat; 74 | 75 | begin 76 | cmd <= "0000"; 77 | wait until rst = '0'; 78 | wait until clk = '1'; 79 | assert done = '1'; 80 | 81 | -- Test period of each rotation. 82 | -- I.e. verify that after repeating the rotation we get back the original cube. 83 | repeat(C_CMD_FP, 4); assert done = '1'; 84 | repeat("0000", 1); 85 | repeat(C_CMD_F2, 2); assert done = '1'; 86 | repeat("0000", 1); 87 | repeat(C_CMD_FM, 4); assert done = '1'; 88 | repeat("0000", 2); 89 | 90 | repeat(C_CMD_RP, 4); assert done = '1'; 91 | repeat("0000", 1); 92 | repeat(C_CMD_R2, 2); assert done = '1'; 93 | repeat("0000", 1); 94 | repeat(C_CMD_RM, 4); assert done = '1'; 95 | repeat("0000", 2); 96 | 97 | repeat(C_CMD_UP, 4); assert done = '1'; 98 | repeat("0000", 1); 99 | repeat(C_CMD_U2, 2); assert done = '1'; 100 | repeat("0000", 1); 101 | repeat(C_CMD_UM, 4); assert done = '1'; 102 | repeat("0000", 2); 103 | 104 | -- Generate some random rotations. 105 | -- Any illegal commands will just be skipped. 106 | for i in 1 to 50 loop 107 | repeat(prbs255(3 downto 0), 1); 108 | end loop; 109 | 110 | wait; 111 | end process p_test; 112 | 113 | i_rubik : entity work.rubik 114 | port map ( 115 | clk_i => clk, 116 | rst_i => rst, 117 | cmd_i => cmd, 118 | done_o => done 119 | ); -- i_rubik 120 | 121 | end architecture simulation; 122 | 123 | -------------------------------------------------------------------------------- /rubik2/swapped.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MJoergen/formal/9e0bc6c20361925199b3a94eb3dd362aba29b010/rubik2/swapped.png -------------------------------------------------------------------------------- /rubik2/twisted.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MJoergen/formal/9e0bc6c20361925199b3a94eb3dd362aba29b010/rubik2/twisted.png -------------------------------------------------------------------------------- /rubik2/waveform.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MJoergen/formal/9e0bc6c20361925199b3a94eb3dd362aba29b010/rubik2/waveform.png -------------------------------------------------------------------------------- /two_stage_buffer/Makefile: -------------------------------------------------------------------------------- 1 | # Type 'make formal' to run formal verification 2 | # Type 'make synth' to run synthesis 3 | 4 | DUT = two_stage_buffer 5 | SRC += ../one_stage_buffer/one_stage_buffer.vhd 6 | SRC += $(DUT).vhd 7 | 8 | 9 | ####################### 10 | # Formal verification 11 | ####################### 12 | 13 | .PHONY: formal 14 | formal: $(DUT)_cover/PASS $(DUT)_prove/PASS 15 | $(DUT)_cover/PASS: $(DUT).sby $(DUT).psl $(SRC) 16 | # This is the main command line to run the formal verification 17 | sby --yosys "yosys -m ghdl" -f $(DUT).sby 18 | 19 | show_prove: 20 | gtkwave $(DUT)_prove/engine_0/trace_induct.vcd $(DUT).gtkw 21 | 22 | 23 | ####################### 24 | # Synthesis 25 | ####################### 26 | 27 | .PHONY: synth 28 | synth: work-obj08.cf 29 | yosys -m ghdl -p 'ghdl -fpsl -fsynopsys --std=08 $(DUT); synth_xilinx -top $(DUT) -edif $(DUT).edif' > yosys.log 30 | 31 | work-obj08.cf: $(SRC) 32 | ghdl -a -fpsl -fsynopsys --std=08 $^ 33 | 34 | 35 | ####################### 36 | # Cleanup 37 | ####################### 38 | 39 | .PHONY: clean 40 | clean: 41 | rm -rf $(DUT)_cover/ 42 | rm -rf $(DUT)_prove/ 43 | rm -rf work-obj08.cf 44 | rm -rf yosys.log 45 | rm -rf $(DUT).edif 46 | 47 | -------------------------------------------------------------------------------- /two_stage_buffer/README.md: -------------------------------------------------------------------------------- 1 | # Two Stage Buffer 2 | The [One Stage Buffer](../one_stage_buffer) is convenient with its zero 3 | latency, but sometimes additional buffering capacity is needed. This is 4 | exactly what the [Two Stage Buffer](two_stage_buffer.vhd) provides. 5 | 6 | ## Implementation 7 | Instead of writing a state machine, I've chosen the hierarchical approch where 8 | I instantiate two copies of the [One Stage Buffer](../one_stage_buffer) and 9 | string them together. Since each of them have zero latency, the combination 10 | will too, and therefore this approach works nicely. Some extra logic is needed 11 | to calculate the total filling of the buffer. 12 | 13 | ## Formal verification 14 | The formal verification is very similar to the [One Stage 15 | Buffer](../one_stage_buffer/one_stage_buffer.psl). 16 | 17 | ## Running formal verification 18 | ![Waveform](waveform.png) 19 | 20 | ## Synthesis 21 | ``` 22 | Number of cells: 68 23 | BUFG 1 24 | FDRE 18 25 | IBUF 12 26 | LUT2 3 27 | LUT3 22 28 | OBUF 12 29 | 30 | Estimated number of LCs: 22 31 | ``` 32 | 33 | Once again just 18 registers, but this time a total LUT count of 25. 34 | 35 | -------------------------------------------------------------------------------- /two_stage_buffer/two_stage_buffer.gtkw: -------------------------------------------------------------------------------- 1 | [*] 2 | [*] GTKWave Analyzer v3.3.103 (w)1999-2019 BSI 3 | [*] Wed Jan 6 19:55:27 2021 4 | [*] 5 | [dumpfile] "/home/mike/git/MJoergen/formal/two_stage_buffer/two_stage_buffer_cover/engine_0/trace5.vcd" 6 | [dumpfile_mtime] "Wed Jan 6 19:54:51 2021" 7 | [dumpfile_size] 4038 8 | [savefile] "/home/mike/git/MJoergen/formal/two_stage_buffer/two_stage_buffer.gtkw" 9 | [timestart] 0 10 | [size] 1424 746 11 | [pos] -1 -1 12 | *-3.786691 35 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 13 | [treeopen] two_stage_buffer. 14 | [sst_width] 249 15 | [signals_width] 244 16 | [sst_expanded] 1 17 | [sst_vpaned_height] 196 18 | @28 19 | two_stage_buffer.clk_i 20 | two_stage_buffer.rst_i 21 | [color] 2 22 | two_stage_buffer.s_valid_i 23 | [color] 7 24 | two_stage_buffer.s_ready_o 25 | @22 26 | two_stage_buffer.s_data_i[7:0] 27 | @28 28 | two_stage_buffer.s_fill_o[1:0] 29 | [color] 2 30 | two_stage_buffer.m_valid_o 31 | [color] 7 32 | two_stage_buffer.m_ready_i 33 | @22 34 | two_stage_buffer.m_data_o[7:0] 35 | @200 36 | - 37 | @24 38 | two_stage_buffer.i_two_stage_buffer.f_count[1:0] 39 | @200 40 | - 41 | @28 42 | [color] 2 43 | two_stage_buffer.int_valid 44 | [color] 7 45 | two_stage_buffer.int_ready 46 | @22 47 | two_stage_buffer.int_data[7:0] 48 | @28 49 | two_stage_buffer.int_afull 50 | two_stage_buffer.s_afull 51 | [pattern_trace] 1 52 | [pattern_trace] 0 53 | -------------------------------------------------------------------------------- /two_stage_buffer/two_stage_buffer.psl: -------------------------------------------------------------------------------- 1 | vunit i_two_stage_buffer(two_stage_buffer(synthesis)) 2 | { 3 | -- Additional signals used during formal verification 4 | signal f_count : integer range 0 to 3 := 0; 5 | 6 | 7 | -- set all declarations to run on clk_i 8 | default clock is rising_edge(clk_i); 9 | 10 | 11 | ----------------------------- 12 | -- ASSERTIONS ABOUT OUTPUTS 13 | ----------------------------- 14 | 15 | -- BUFFER must be empty after reset, unless incoming data 16 | f_after_reset_empty : assert always {rst_i} |=> {not m_valid_o} abort s_valid_i; 17 | 18 | -- BUFFER must be ready after reset 19 | f_after_reset_ready : assert always {rst_i} |=> s_ready_o; 20 | 21 | -- Output must be stable until accepted or reset 22 | f_output_stable : assert always {m_valid_o and not m_ready_i and not rst_i} |=> {stable(m_valid_o) and stable(m_data_o)}; 23 | 24 | -- Ready must be stable until new data 25 | f_ready_stable : assert always {s_ready_o and not s_valid_i and not rst_i} |=> {stable(s_ready_o)}; 26 | 27 | -- Keep track of amount of data flowing into and out of the BUFFER 28 | p_count : process (clk_i) 29 | begin 30 | if rising_edge(clk_i) then 31 | -- Data flowing in, but not out. 32 | if s_valid_i and s_ready_o and not (m_valid_o and m_ready_i) then 33 | f_count <= f_count + 1; 34 | end if; 35 | 36 | -- Data flowing out, but not in. 37 | if m_valid_o and m_ready_i and not (s_valid_i and s_ready_o) then 38 | f_count <= f_count - 1; 39 | end if; 40 | 41 | if rst_i then 42 | f_count <= 0; 43 | end if; 44 | end if; 45 | end process p_count; 46 | 47 | -- The BUFFER size is limited to 2. 48 | f_size : assert always {0 <= f_count and f_count <= 2}; 49 | 50 | -- If BUFFER is full, ready must be transparent 51 | f_count_2 : assert always {f_count >= 2} |-> {s_ready_o = m_ready_i} abort rst_i; 52 | 53 | -- If BUFFER is almost full, it must always present valid data on output 54 | f_count_1 : assert always {f_count >= 1} |-> {m_valid_o = '1'} abort rst_i; 55 | 56 | -- If BUFFER is empty and no input, no data present on output 57 | f_count_0 : assert always {f_count = 0 and s_valid_i = '0'} |-> {m_valid_o = '0'} abort rst_i; 58 | 59 | -- If BUFFER is empty and with input, valid data present on output 60 | f_count_0_input : assert always {f_count = 0 and s_valid_i = '1'} |-> {m_valid_o = '1' and m_data_o = s_data_i} abort rst_i; 61 | 62 | -- Verify BUFFER filling 63 | f_fill : assert always {f_count = to_integer(s_fill_o)}; 64 | 65 | -- Verify internal signal. This is needed for induction proof 66 | f_internal : assert always {not int_afull} |-> {not s_afull}; 67 | 68 | 69 | ----------------------------- 70 | -- ASSUMPTIONS ABOUT INPUTS 71 | ----------------------------- 72 | 73 | -- Require reset at startup. 74 | f_reset : assume {rst_i}; 75 | 76 | -- Assume input is stable 77 | f_input_stable : assume always {s_valid_i and not s_ready_o} |=> {stable(s_valid_i) and stable(s_data_i)}; 78 | 79 | 80 | -------------------------------------------- 81 | -- COVER STATEMENTS TO VERIFY REACHABILITY 82 | -------------------------------------------- 83 | 84 | -- Make sure BUFFER can transition from full to empty. 85 | f_full_to_empty : cover {f_count = 2; f_count = 1; f_count = 0}; 86 | 87 | } -- vunit i_two_stage_buffer(two_stage_buffer(synthesis)) 88 | 89 | -------------------------------------------------------------------------------- /two_stage_buffer/two_stage_buffer.sby: -------------------------------------------------------------------------------- 1 | [tasks] 2 | cover 3 | prove 4 | 5 | [options] 6 | cover: mode cover 7 | prove: mode prove 8 | prove: depth 4 9 | 10 | [engines] 11 | smtbmc 12 | 13 | [script] 14 | ghdl --std=08 two_stage_buffer.vhd one_stage_buffer.vhd one_stage_buffer.psl two_stage_buffer.psl -e two_stage_buffer 15 | prep -top two_stage_buffer 16 | chformal -assume2assert two_stage_buffer/* %M 17 | 18 | [files] 19 | two_stage_buffer.psl 20 | two_stage_buffer.vhd 21 | ../one_stage_buffer/one_stage_buffer.psl 22 | ../one_stage_buffer/one_stage_buffer.vhd 23 | 24 | -------------------------------------------------------------------------------- /two_stage_buffer/two_stage_buffer.vhd: -------------------------------------------------------------------------------- 1 | -- An elastic pipeline with two stages, with zero latency. 2 | -- It can accept two writes before blocking, i.e. a FIFO of depth two. 3 | 4 | library ieee; 5 | use ieee.std_logic_1164.all; 6 | use ieee.numeric_std_unsigned.all; 7 | 8 | entity two_stage_buffer is 9 | generic ( 10 | G_DATA_SIZE : integer := 8 11 | ); 12 | port ( 13 | clk_i : in std_logic; 14 | rst_i : in std_logic; 15 | s_valid_i : in std_logic; 16 | s_ready_o : out std_logic; 17 | s_data_i : in std_logic_vector(G_DATA_SIZE-1 downto 0); 18 | s_fill_o : out std_logic_vector(1 downto 0); 19 | m_valid_o : out std_logic; 20 | m_ready_i : in std_logic; 21 | m_data_o : out std_logic_vector(G_DATA_SIZE-1 downto 0) 22 | ); 23 | end entity two_stage_buffer; 24 | 25 | architecture synthesis of two_stage_buffer is 26 | 27 | signal int_valid : std_logic; 28 | signal int_ready : std_logic; 29 | signal int_data : std_logic_vector(G_DATA_SIZE-1 downto 0); 30 | signal int_afull : std_logic; 31 | signal s_afull : std_logic; 32 | 33 | begin 34 | 35 | s_fill_o <= "00" when not int_afull else 36 | "01" when not s_afull else 37 | "10"; 38 | 39 | i_osb_first : entity work.one_stage_buffer 40 | generic map ( 41 | G_DATA_SIZE => G_DATA_SIZE 42 | ) 43 | port map ( 44 | clk_i => clk_i, 45 | rst_i => rst_i, 46 | s_valid_i => s_valid_i, 47 | s_ready_o => s_ready_o, 48 | s_data_i => s_data_i, 49 | s_afull_o => s_afull, 50 | m_valid_o => int_valid, 51 | m_ready_i => int_ready, 52 | m_data_o => int_data 53 | ); -- i_osb_first 54 | 55 | i_osb_second : entity work.one_stage_buffer 56 | generic map ( 57 | G_DATA_SIZE => G_DATA_SIZE 58 | ) 59 | port map ( 60 | clk_i => clk_i, 61 | rst_i => rst_i, 62 | s_valid_i => int_valid, 63 | s_ready_o => int_ready, 64 | s_data_i => int_data, 65 | s_afull_o => int_afull, 66 | m_valid_o => m_valid_o, 67 | m_ready_i => m_ready_i, 68 | m_data_o => m_data_o 69 | ); -- i_osb_second 70 | 71 | end architecture synthesis; 72 | 73 | -------------------------------------------------------------------------------- /two_stage_buffer/waveform.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MJoergen/formal/9e0bc6c20361925199b3a94eb3dd362aba29b010/two_stage_buffer/waveform.png -------------------------------------------------------------------------------- /two_stage_fifo/Makefile: -------------------------------------------------------------------------------- 1 | # Type 'make formal' to run formal verification 2 | # Type 'make synth' to run synthesis 3 | 4 | DUT = two_stage_fifo 5 | SRC += $(DUT).vhd 6 | 7 | 8 | ####################### 9 | # Formal verification 10 | ####################### 11 | 12 | .PHONY: formal 13 | formal: $(DUT)_cover/PASS $(DUT)_prove/PASS 14 | $(DUT)_cover/PASS: $(DUT).sby $(DUT).psl $(SRC) 15 | # This is the main command line to run the formal verification 16 | sby --yosys "yosys -m ghdl" -f $(DUT).sby 17 | 18 | show_prove: 19 | gtkwave $(DUT)_prove/engine_0/trace_induct.vcd $(DUT).gtkw 20 | 21 | 22 | ####################### 23 | # Synthesis 24 | ####################### 25 | 26 | .PHONY: synth 27 | synth: work-obj08.cf 28 | yosys -m ghdl -p 'ghdl -fpsl -fsynopsys --std=08 $(DUT); synth_xilinx -top $(DUT) -edif $(DUT).edif' > yosys.log 29 | 30 | work-obj08.cf: $(SRC) 31 | ghdl -a -fpsl -fsynopsys --std=08 $^ 32 | 33 | 34 | ####################### 35 | # Cleanup 36 | ####################### 37 | 38 | .PHONY: clean 39 | clean: 40 | rm -rf $(DUT)_cover/ 41 | rm -rf $(DUT)_prove/ 42 | rm -rf work-obj08.cf 43 | rm -rf yosys.log 44 | rm -rf $(DUT).edif 45 | 46 | -------------------------------------------------------------------------------- /two_stage_fifo/README.md: -------------------------------------------------------------------------------- 1 | # Two Stage Fifo 2 | The [One Stage FIFO](../one_stage_fifo) has a combinatorial path on the 3 | upstream READY signal. This can be a problem in large designs, where such long 4 | combinatorial paths lead to timing problems. To get a register on the READY 5 | signal it is necessary to use a [Two Stage FIFO](two_stage_fifo.vhd). 6 | 7 | The interface to the module is the same as for the [One Stage 8 | FIFO](../one_stage_fifo), except that I've added an extra signal `s_fill_o`. 9 | This provides information (to the producer) about the current filling level of 10 | the FIFO. I.e. it is a value from zero to two. There may be situations where 11 | the producer needs to know the current filling in order to predict whether the 12 | FIFO can accept two or more consecutive cycles of data. 13 | 14 | ## The implementation 15 | Not surprisingly, the implementation has exactly two sets of data registers, 16 | and additionally two registers for the output control signals. 17 | 18 | I've chosen not to have a separate register for the current filling level, so 19 | instead that is calculated combinatorially from the control signals. 20 | 21 | ## Formal verification 22 | The formal verification is very similar to that of the [One Stage 23 | FIFO](../one_stage_fifo). The main change is that the calculated FIFO size is 24 | now verified to match the output signal `s_fill_o`. 25 | 26 | The cover statement I've now chosen to show the FIFO filling transition from 27 | 2->1->0. 28 | 29 | ## Running formal verification 30 | The result of running formal verification (by typing `make') is shown in this waveform: 31 | ![Waveform](waveform.png) 32 | 33 | ## Synthesis 34 | Running `make synth` shows the following resource utilization: 35 | ``` 36 | Number of cells: 55 37 | BUFG 1 38 | FDRE 17 39 | FDSE 1 40 | IBUF 12 41 | LUT2 2 42 | LUT3 2 43 | LUT6 8 44 | OBUF 12 45 | 46 | Estimated number of LCs: 10 47 | ``` 48 | 49 | As expected, we see a total of 18 registers. The total LUT count is 12. 50 | 51 | -------------------------------------------------------------------------------- /two_stage_fifo/two_stage_fifo.gtkw: -------------------------------------------------------------------------------- 1 | [*] 2 | [*] GTKWave Analyzer v3.3.103 (w)1999-2019 BSI 3 | [*] Wed Jan 6 19:36:47 2021 4 | [*] 5 | [dumpfile] "/home/mike/git/MJoergen/formal/two_stage_fifo/two_stage_fifo_cover/engine_0/trace4.vcd" 6 | [dumpfile_mtime] "Wed Jan 6 19:36:07 2021" 7 | [dumpfile_size] 1723 8 | [savefile] "/home/mike/git/MJoergen/formal/two_stage_fifo/two_stage_fifo.gtkw" 9 | [timestart] 0 10 | [size] 1424 746 11 | [pos] -1 -1 12 | *-3.786691 15 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 13 | [treeopen] two_stage_fifo. 14 | [sst_width] 249 15 | [signals_width] 244 16 | [sst_expanded] 1 17 | [sst_vpaned_height] 196 18 | @28 19 | two_stage_fifo.clk_i 20 | two_stage_fifo.rst_i 21 | [color] 2 22 | two_stage_fifo.s_valid_i 23 | [color] 7 24 | two_stage_fifo.s_ready_o 25 | @22 26 | two_stage_fifo.s_data_i[7:0] 27 | @28 28 | two_stage_fifo.s_fill_o[1:0] 29 | [color] 2 30 | two_stage_fifo.m_valid_o 31 | [color] 7 32 | two_stage_fifo.m_ready_i 33 | @22 34 | two_stage_fifo.m_data_o[7:0] 35 | @200 36 | - 37 | @24 38 | two_stage_fifo.i_two_stage_fifo.f_count[1:0] 39 | [pattern_trace] 1 40 | [pattern_trace] 0 41 | -------------------------------------------------------------------------------- /two_stage_fifo/two_stage_fifo.psl: -------------------------------------------------------------------------------- 1 | vunit i_two_stage_fifo(two_stage_fifo(synthesis)) 2 | { 3 | -- Additional signals used during formal verification 4 | signal f_count : integer range 0 to 3 := 0; 5 | 6 | 7 | -- set all declarations to run on clk_i 8 | default clock is rising_edge(clk_i); 9 | 10 | 11 | ----------------------------- 12 | -- ASSERTIONS ABOUT OUTPUTS 13 | ----------------------------- 14 | 15 | -- FIFO must be empty after reset 16 | f_after_reset_empty : assert always {rst_i} |=> not m_valid_o; 17 | 18 | -- FIFO must be ready after reset 19 | f_after_reset_ready : assert always {rst_i} |=> s_ready_o; 20 | 21 | -- Output must be stable until accepted 22 | f_output_stable : assert always {m_valid_o and not m_ready_i and not rst_i} |=> {stable(m_valid_o) and stable(m_data_o)}; 23 | 24 | -- Ready must be stable until new data 25 | f_ready_stable : assert always {s_ready_o and not s_valid_i and not rst_i} |=> {stable(s_ready_o)}; 26 | 27 | -- Keep track of amount of data flowing into and out of the FIFO 28 | p_count : process (clk_i) 29 | begin 30 | if rising_edge(clk_i) then 31 | -- Data flowing in, but not out. 32 | if s_valid_i and s_ready_o and not (m_valid_o and m_ready_i) then 33 | f_count <= f_count + 1; 34 | end if; 35 | 36 | -- Data flowing out, but not in. 37 | if m_valid_o and m_ready_i and not (s_valid_i and s_ready_o) then 38 | f_count <= f_count - 1; 39 | end if; 40 | 41 | if rst_i then 42 | f_count <= 0; 43 | end if; 44 | end if; 45 | end process p_count; 46 | 47 | -- The FIFO size is limited to 2. 48 | f_size : assert always {0 <= f_count and f_count <= 2}; 49 | 50 | -- The FIFO size is as expected 51 | f_fill : assert always {f_count = to_integer(s_fill_o)}; 52 | 53 | 54 | ----------------------------- 55 | -- ASSUMPTIONS ABOUT INPUTS 56 | ----------------------------- 57 | 58 | -- Require reset at startup. 59 | f_reset : assume {rst_i}; 60 | 61 | 62 | -------------------------------------------- 63 | -- COVER STATEMENTS TO VERIFY REACHABILITY 64 | -------------------------------------------- 65 | 66 | -- Make sure FIFO can transition from full to empty. 67 | f_full_to_empty : cover {f_count = 2; f_count = 1; f_count = 0}; 68 | 69 | } -- vunit i_two_stage_fifo(two_stage_fifo(synthesis)) 70 | 71 | -------------------------------------------------------------------------------- /two_stage_fifo/two_stage_fifo.sby: -------------------------------------------------------------------------------- 1 | [tasks] 2 | cover 3 | prove 4 | 5 | [options] 6 | cover: mode cover 7 | prove: mode prove 8 | prove: depth 4 9 | 10 | [engines] 11 | smtbmc 12 | 13 | [script] 14 | ghdl --std=08 two_stage_fifo.vhd two_stage_fifo.psl -e two_stage_fifo 15 | prep -top two_stage_fifo 16 | 17 | [files] 18 | two_stage_fifo.psl 19 | two_stage_fifo.vhd 20 | 21 | -------------------------------------------------------------------------------- /two_stage_fifo/two_stage_fifo.vhd: -------------------------------------------------------------------------------- 1 | -- An elastic pipeline with two stages. I.e. can accept two writes before blocking. 2 | -- In other words, a FIFO of depth two. 3 | 4 | library ieee; 5 | use ieee.std_logic_1164.all; 6 | use ieee.numeric_std_unsigned.all; 7 | 8 | entity two_stage_fifo is 9 | generic ( 10 | G_DATA_SIZE : integer := 8 11 | ); 12 | port ( 13 | clk_i : in std_logic; 14 | rst_i : in std_logic; 15 | s_valid_i : in std_logic; 16 | s_ready_o : out std_logic; 17 | s_data_i : in std_logic_vector(G_DATA_SIZE-1 downto 0); 18 | s_fill_o : out std_logic_vector(1 downto 0); 19 | m_valid_o : out std_logic; 20 | m_ready_i : in std_logic; 21 | m_data_o : out std_logic_vector(G_DATA_SIZE-1 downto 0) 22 | ); 23 | end entity two_stage_fifo; 24 | 25 | architecture synthesis of two_stage_fifo is 26 | 27 | -- Input registers 28 | signal s_data_r : std_logic_vector(G_DATA_SIZE-1 downto 0); 29 | 30 | -- Output registers 31 | signal m_data_r : std_logic_vector(G_DATA_SIZE-1 downto 0); 32 | 33 | -- Control signals 34 | signal s_ready_r : std_logic := '1'; 35 | signal m_valid_r : std_logic := '0'; 36 | 37 | begin 38 | 39 | s_fill_o <= "00" when m_valid_o = '0' else 40 | "01" when m_valid_o = '1' and s_ready_o = '1' else 41 | "10"; -- when m_valid_o = '1' and s_ready_o = '0' 42 | 43 | 44 | p_s_data : process (clk_i) 45 | begin 46 | if rising_edge(clk_i) then 47 | if s_ready_r = '1' then 48 | s_data_r <= s_data_i; 49 | end if; 50 | end if; 51 | end process p_s_data; 52 | 53 | 54 | p_s_ready : process (clk_i) 55 | begin 56 | if rising_edge(clk_i) then 57 | if m_valid_r = '1' then 58 | s_ready_r <= m_ready_i or (s_ready_r and not s_valid_i); 59 | end if; 60 | 61 | if rst_i = '1' then 62 | s_ready_r <= '1'; 63 | end if; 64 | end if; 65 | end process p_s_ready; 66 | 67 | 68 | p_m : process (clk_i) 69 | begin 70 | if rising_edge(clk_i) then 71 | if s_ready_r = '1' then 72 | if m_valid_r = '0' or m_ready_i = '1' then 73 | m_valid_r <= s_valid_i; 74 | m_data_r <= s_data_i; 75 | end if; 76 | else 77 | if m_ready_i = '1' then 78 | m_data_r <= s_data_r; 79 | end if; 80 | end if; 81 | 82 | if rst_i = '1' then 83 | m_valid_r <= '0'; 84 | end if; 85 | end if; 86 | end process p_m; 87 | 88 | 89 | -------------------------- 90 | -- Connect output signals 91 | -------------------------- 92 | 93 | s_ready_o <= s_ready_r; 94 | m_valid_o <= m_valid_r; 95 | m_data_o <= m_data_r; 96 | 97 | end architecture synthesis; 98 | 99 | -------------------------------------------------------------------------------- /two_stage_fifo/waveform.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MJoergen/formal/9e0bc6c20361925199b3a94eb3dd362aba29b010/two_stage_fifo/waveform.png -------------------------------------------------------------------------------- /wb_mem/Makefile: -------------------------------------------------------------------------------- 1 | DUT = wb_mem 2 | 3 | # This is the main command line to run the formal verification 4 | all: 5 | sby --yosys "yosys -m ghdl" -f $(DUT).sby 6 | 7 | show_bmc: 8 | gtkwave $(DUT)_bmc/engine_0/trace.vcd $(DUT).gtkw 9 | 10 | show_cover: 11 | gtkwave $(DUT)_cover/engine_0/trace3.vcd $(DUT).gtkw 12 | 13 | show_prove: 14 | gtkwave $(DUT)_prove/engine_0/trace_induct.vcd $(DUT).gtkw 15 | 16 | clean: 17 | rm -rf $(DUT)_bmc/ 18 | rm -rf $(DUT)_cover/ 19 | rm -rf $(DUT)_prove/ 20 | rm -rf work-obj08.cf 21 | rm -rf yosys.log 22 | rm -rf $(DUT).edif 23 | 24 | # Synthesis 25 | 26 | work-obj08.cf: $(DUT).vhd 27 | ghdl -a -fpsl --std=08 $^ 28 | 29 | synth: work-obj08.cf 30 | yosys -m ghdl -p 'ghdl -fpsl --std=08 $(DUT); synth_xilinx -top $(DUT) -family xc7 -edif $(DUT).edif' > yosys.log 31 | 32 | -------------------------------------------------------------------------------- /wb_mem/README.md: -------------------------------------------------------------------------------- 1 | # A simple memory with a Wishbone interface 2 | In order to gain more experience with formal verification I've decided to 3 | implement another simple module. I chose a small memory with a Wishbone 4 | slave interface, so as to learn about that bus protocol too. 5 | 6 | ## Wishbone slave requirements 7 | This wishbone memory module has a number of easy-to-test requirements: 8 | 9 | When `wb_ack_o` is de-asserted, then `wb_data_o` is all zeros. 10 | ``` 11 | f_data_zero : assert always {not wb_ack_o} |-> {or(wb_data_o) = '0'}; 12 | ``` 13 | 14 | When writing to memory, the data output is unchanged. 15 | ``` 16 | f_data_stable : assert always {wb_cyc_i and wb_stb_i and wb_we_i and not rst_i} |=> {stable(wb_data_o)}; 17 | ``` 18 | 19 | The response always appears on the exact following clock cycle, i.e. a fixed latency of 1. 20 | ``` 21 | f_ack_next : assert always {wb_cyc_i and wb_stb_i and wb_we_i and not rst_i} |=> {wb_ack_o}; 22 | ``` 23 | 24 | No ACKs allowed when the bus is idle. 25 | ``` 26 | f_ack_idle : assert always {not (wb_cyc_i and wb_stb_i)} |=> {not wb_ack_o}; 27 | ``` 28 | 29 | At most one outstanding request. 30 | ``` 31 | f_outstanding : assert always {0 <= f_count and f_count <= 1}; 32 | ``` 33 | Here I additionally need to keep a track of the number of outstanding requests as follows: 34 | ``` 35 | p_count : process (clk_i) 36 | begin 37 | if rising_edge(clk_i) then 38 | -- Request without response 39 | if wb_cyc_i and wb_stb_i and not (wb_ack_o) then 40 | f_count <= f_count + 1; 41 | end if; 42 | 43 | -- Reponse without request 44 | if not(wb_cyc_i and wb_stb_i) and wb_ack_o then 45 | f_count <= f_count - 1; 46 | end if; 47 | 48 | if rst_i or not wb_cyc_i then 49 | f_count <= 0; 50 | end if; 51 | end if; 52 | end process p_count; 53 | ``` 54 | 55 | No ACK without outstanding request 56 | ``` 57 | f_count_0 : assert always {f_count = 0} |-> {not wb_ack_o}; 58 | ``` 59 | 60 | ACK always comes immediately after an outstanding request 61 | ``` 62 | f_count_1 : assert always {f_count = 1} |-> {wb_ack_o}; 63 | ``` 64 | 65 | Low CYC aborts all transactions 66 | ``` 67 | f_idle : assert always {not wb_cyc_i} |=> {f_count = 0}; 68 | ``` 69 | 70 | -------------------------------------------------------------------------------- /wb_mem/wb_mem.gtkw: -------------------------------------------------------------------------------- 1 | [*] 2 | [*] GTKWave Analyzer v3.3.103 (w)1999-2019 BSI 3 | [*] Tue Dec 29 19:58:41 2020 4 | [*] 5 | [dumpfile] "/home/mike/git/MJoergen/formal/wb_mem/wb_mem_cover/engine_0/trace2.vcd" 6 | [dumpfile_mtime] "Tue Dec 29 19:57:40 2020" 7 | [dumpfile_size] 1577 8 | [savefile] "/home/mike/git/MJoergen/formal/wb_mem/wb_mem.gtkw" 9 | [timestart] 0 10 | [size] 1920 1132 11 | [pos] -1 -1 12 | *-3.325367 27 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 13 | [treeopen] wb_mem. 14 | [sst_width] 249 15 | [signals_width] 285 16 | [sst_expanded] 1 17 | [sst_vpaned_height] 343 18 | @28 19 | wb_mem.clk_i 20 | wb_mem.rst_i 21 | [color] 2 22 | wb_mem.wb_cyc_i 23 | [color] 2 24 | wb_mem.wb_stb_i 25 | [color] 7 26 | wb_mem.wb_stall_o 27 | wb_mem.wb_we_i 28 | @22 29 | wb_mem.wb_addr_i[7:0] 30 | wb_mem.wb_data_i[15:0] 31 | @28 32 | [color] 2 33 | wb_mem.wb_ack_o 34 | @22 35 | wb_mem.wb_data_o[15:0] 36 | @200 37 | - 38 | @28 39 | wb_mem.formal_gen.f_rst 40 | @25 41 | wb_mem.formal_gen.f_count[1:0] 42 | [pattern_trace] 1 43 | [pattern_trace] 0 44 | -------------------------------------------------------------------------------- /wb_mem/wb_mem.psl: -------------------------------------------------------------------------------- 1 | vunit i_wb_mem(wb_mem(synthesis)) 2 | { 3 | -- Additional signals used during formal verification 4 | signal f_count : integer range 0 to 3 := 0; 5 | 6 | 7 | -- set all declarations to run on clk_i 8 | default clock is rising_edge(clk_i); 9 | 10 | 11 | ----------------------------- 12 | -- ASSERTIONS ABOUT OUTPUTS 13 | ----------------------------- 14 | 15 | -- When wb_ack_o is de-asserted, then wb_data_o is all zeros. 16 | f_data_zero : assert always {not wb_ack_o} |-> {or(wb_data_o) = '0'}; 17 | 18 | -- When writing to memory, the data output is unchanged. 19 | f_data_stable : assert always {wb_cyc_i and wb_stb_i and wb_we_i and not rst_i} |=> {stable(wb_data_o)}; 20 | 21 | -- The response always appears on the exact following clock cycle, i.e. a fixed latency of 1. 22 | f_ack_next : assert always {wb_cyc_i and wb_stb_i and wb_we_i and not rst_i} |=> {wb_ack_o}; 23 | 24 | -- No ACKs allowed when the bus is idle. 25 | f_ack_idle : assert always {not (wb_cyc_i and wb_stb_i)} |=> {not wb_ack_o}; 26 | 27 | -- Keep track of outstanding requests 28 | p_count : process (clk_i) 29 | begin 30 | if rising_edge(clk_i) then 31 | -- Request without response 32 | if wb_cyc_i and wb_stb_i and not (wb_ack_o) then 33 | f_count <= f_count + 1; 34 | end if; 35 | 36 | -- Reponse without request 37 | if not(wb_cyc_i and wb_stb_i) and wb_ack_o then 38 | f_count <= f_count - 1; 39 | end if; 40 | 41 | if rst_i or not wb_cyc_i then 42 | f_count <= 0; 43 | end if; 44 | end if; 45 | end process p_count; 46 | 47 | -- At most one outstanding request 48 | f_outstanding : assert always {0 <= f_count and f_count <= 1}; 49 | 50 | -- No ACK without outstanding request 51 | f_count_0 : assert always {f_count = 0} |-> {not wb_ack_o}; 52 | 53 | -- ACK always comes immediately after an outstanding request 54 | f_count_1 : assert always {f_count = 1} |-> {wb_ack_o}; 55 | 56 | -- Low CYC aborts all transactions 57 | f_idle : assert always {not wb_cyc_i} |=> {f_count = 0}; 58 | 59 | 60 | ----------------------------- 61 | -- ASSUMPTIONS ABOUT INPUTS 62 | ----------------------------- 63 | 64 | -- Require reset at startup. 65 | f_reset : assume {rst_i}; 66 | 67 | 68 | -------------------------------------------- 69 | -- COVER STATEMENTS TO VERIFY REACHABILITY 70 | -------------------------------------------- 71 | 72 | -- Make sure memory can respond to a request 73 | f_full_to_empty : cover {f_count = 1; f_count = 0}; 74 | 75 | } -- vunit i_wb_mem(wb_mem(synthesis)) 76 | 77 | -------------------------------------------------------------------------------- /wb_mem/wb_mem.sby: -------------------------------------------------------------------------------- 1 | [tasks] 2 | bmc 3 | cover 4 | prove 5 | 6 | [options] 7 | bmc: mode bmc 8 | bmc: depth 10 9 | cover: mode cover 10 | prove: mode prove 11 | prove: depth 4 12 | 13 | [engines] 14 | smtbmc 15 | 16 | [script] 17 | ghdl --std=08 -frelaxed wb_mem.vhd wb_mem.psl -e wb_mem 18 | prep -top wb_mem 19 | 20 | [files] 21 | wb_mem.vhd 22 | wb_mem.psl 23 | 24 | -------------------------------------------------------------------------------- /wb_mem/wb_mem.vhd: -------------------------------------------------------------------------------- 1 | library ieee; 2 | use ieee.std_logic_1164.all; 3 | use ieee.numeric_std.all; 4 | use std.textio.all; 5 | 6 | -- A simple memory with a Wishbone Slave interface. 7 | 8 | entity wb_mem is 9 | generic ( 10 | G_ROM_FILE : string := ""; 11 | G_ADDR_SIZE : integer := 8; 12 | G_DATA_SIZE : integer := 16 13 | ); 14 | port ( 15 | clk_i : in std_logic; 16 | rst_i : in std_logic; 17 | wb_cyc_i : in std_logic; 18 | wb_stall_o : out std_logic; 19 | wb_stb_i : in std_logic; 20 | wb_ack_o : out std_logic; 21 | wb_we_i : in std_logic; 22 | wb_addr_i : in std_logic_vector(G_ADDR_SIZE-1 downto 0); 23 | wb_data_i : in std_logic_vector(G_DATA_SIZE-1 downto 0); 24 | wb_data_o : out std_logic_vector(G_DATA_SIZE-1 downto 0) 25 | ); 26 | end entity wb_mem; 27 | 28 | architecture synthesis of wb_mem is 29 | 30 | type mem_t is array (0 to 2**G_ADDR_SIZE-1) of std_logic_vector(G_DATA_SIZE-1 downto 0); 31 | 32 | -- This reads the ROM contents from a text file 33 | impure function InitRamFromFile(RamFileName : in string) return mem_t is 34 | FILE RamFile : text; 35 | variable RamFileLine : line; 36 | variable ram : mem_t := (others => (others => '0')); 37 | begin 38 | if RamFileName /= "" then 39 | file_open(RamFile, RamFileName, read_mode); 40 | for i in mem_t'range loop 41 | readline (RamFile, RamFileLine); 42 | read (RamFileLine, ram(i)); 43 | if endfile(RamFile) then 44 | return ram; 45 | end if; 46 | end loop; 47 | end if; 48 | return ram; 49 | end function; 50 | 51 | -- Initial memory contents 52 | signal mem_r : mem_t := InitRamFromFile(G_ROM_FILE); 53 | 54 | signal wb_ack_r : std_logic := '0'; 55 | signal wb_data_r : std_logic_vector(G_DATA_SIZE-1 downto 0) := (others => '0'); 56 | 57 | begin 58 | 59 | -- Writing to memory 60 | p_write : process (clk_i) 61 | begin 62 | if rising_edge(clk_i) then 63 | if wb_cyc_i = '1' and wb_stb_i = '1' and wb_stall_o = '0' and wb_we_i = '1' then 64 | mem_r(to_integer(unsigned(wb_addr_i))) <= wb_data_i; 65 | end if; 66 | end if; 67 | end process p_write; 68 | 69 | -- Reading from memory 70 | p_read : process (clk_i) 71 | begin 72 | if rising_edge(clk_i) then 73 | wb_ack_r <= '0'; 74 | 75 | if wb_cyc_i = '1' and wb_stb_i = '1' and wb_stall_o = '0' then 76 | if wb_we_i = '0' then 77 | wb_data_r <= mem_r(to_integer(unsigned(wb_addr_i))); 78 | end if; 79 | wb_ack_r <= '1'; -- This also ACK's the write transaction. 80 | else 81 | wb_data_r <= (others => '0'); 82 | end if; 83 | end if; 84 | end process p_read; 85 | 86 | -- Connect output signals 87 | wb_stall_o <= rst_i; 88 | wb_ack_o <= wb_ack_r; 89 | wb_data_o <= wb_data_r; 90 | 91 | end architecture synthesis; 92 | 93 | -------------------------------------------------------------------------------- /wb_tdp_mem/Makefile: -------------------------------------------------------------------------------- 1 | DUT = wb_tdp_mem 2 | 3 | # This is the main command line to run the formal verification 4 | all: 5 | sby --yosys "yosys -m ghdl" -f $(DUT).sby 6 | 7 | show_bmc: 8 | gtkwave $(DUT)_bmc/engine_0/trace.vcd $(DUT).gtkw 9 | 10 | show_cover: 11 | gtkwave $(DUT)_cover/engine_0/trace3.vcd $(DUT).gtkw 12 | 13 | show_prove: 14 | gtkwave $(DUT)_prove/engine_0/trace_induct.vcd $(DUT).gtkw 15 | 16 | clean: 17 | rm -rf $(DUT)_bmc/ 18 | rm -rf $(DUT)_cover/ 19 | rm -rf $(DUT)_prove/ 20 | rm -rf work-obj08.cf 21 | rm -rf yosys.log 22 | rm -rf $(DUT).edif 23 | 24 | # Synthesis 25 | 26 | work-obj08.cf: $(DUT).vhd 27 | ghdl -a -fpsl --std=08 $^ 28 | 29 | synth: work-obj08.cf 30 | yosys -m ghdl -p 'ghdl -fpsl --std=08 $(DUT); synth_xilinx -top $(DUT) -family xc7 -edif $(DUT).edif' > yosys.log 31 | 32 | -------------------------------------------------------------------------------- /wb_tdp_mem/README.md: -------------------------------------------------------------------------------- 1 | # A True Dual Port memory with a Wishbone interface 2 | -------------------------------------------------------------------------------- /wb_tdp_mem/tdp_ram.vhd: -------------------------------------------------------------------------------- 1 | library ieee; 2 | use ieee.std_logic_1164.all; 3 | use ieee.numeric_std_unsigned.all; 4 | use std.textio.all; 5 | 6 | entity tdp_ram is 7 | generic ( 8 | G_INIT_FILE : string := ""; 9 | G_RAM_STYLE : string := "block"; 10 | G_ADDR_SIZE : integer; 11 | G_DATA_SIZE : integer 12 | ); 13 | port ( 14 | clk_i : in std_logic; 15 | rst_i : in std_logic; 16 | -- Port A 17 | a_addr_i : in std_logic_vector(G_ADDR_SIZE-1 downto 0); 18 | a_wr_en_i : in std_logic; 19 | a_wr_data_i : in std_logic_vector(G_DATA_SIZE-1 downto 0); 20 | a_rd_en_i : in std_logic; 21 | a_rd_data_o : out std_logic_vector(G_DATA_SIZE-1 downto 0) := (others => '0'); 22 | -- Port B 23 | b_addr_i : in std_logic_vector(G_ADDR_SIZE-1 downto 0); 24 | b_wr_en_i : in std_logic; 25 | b_wr_data_i : in std_logic_vector(G_DATA_SIZE-1 downto 0); 26 | b_rd_en_i : in std_logic; 27 | b_rd_data_o : out std_logic_vector(G_DATA_SIZE-1 downto 0) := (others => '0') 28 | ); 29 | end entity tdp_ram; 30 | 31 | architecture synthesis of tdp_ram is 32 | 33 | type ram_t is array (0 to 2**G_ADDR_SIZE-1) of std_logic_vector(G_DATA_SIZE-1 downto 0); 34 | 35 | -- This reads the ROM contents from a text file 36 | impure function InitRamFromFile(RamFileName : in string) return ram_t is 37 | FILE RamFile : text; 38 | variable RamFileLine : line; 39 | variable ram : ram_t := (others => (others => '0')); 40 | begin 41 | if RamFileName /= "" then 42 | file_open(RamFile, RamFileName, read_mode); 43 | for i in ram_t'range loop 44 | readline (RamFile, RamFileLine); 45 | read (RamFileLine, ram(i)); 46 | if endfile(RamFile) then 47 | return ram; 48 | end if; 49 | end loop; 50 | end if; 51 | return ram; 52 | end function; 53 | 54 | -- Initial memory contents 55 | shared variable ram_r : ram_t := InitRamFromFile(G_INIT_FILE); 56 | 57 | attribute ram_style : string; 58 | attribute ram_style of ram_r : variable is G_RAM_STYLE; 59 | 60 | begin 61 | 62 | p_a : process (clk_i) 63 | begin 64 | if rising_edge(clk_i) then 65 | if a_rd_en_i = '1' then 66 | a_rd_data_o <= ram_r(to_integer(a_addr_i)); 67 | end if; 68 | if a_wr_en_i = '1' then 69 | ram_r(to_integer(a_addr_i)) := a_wr_data_i; 70 | end if; 71 | end if; 72 | end process p_a; 73 | 74 | p_b : process (clk_i) 75 | begin 76 | if rising_edge(clk_i) then 77 | if b_rd_en_i = '1' then 78 | b_rd_data_o <= ram_r(to_integer(b_addr_i)); 79 | end if; 80 | if b_wr_en_i = '1' then 81 | ram_r(to_integer(b_addr_i)) := b_wr_data_i; 82 | end if; 83 | end if; 84 | end process p_b; 85 | 86 | end architecture synthesis; 87 | 88 | -------------------------------------------------------------------------------- /wb_tdp_mem/wb_tdp_mem.gtkw: -------------------------------------------------------------------------------- 1 | [*] 2 | [*] GTKWave Analyzer v3.3.103 (w)1999-2019 BSI 3 | [*] Wed Feb 3 12:04:37 2021 4 | [*] 5 | [dumpfile] "/home/mike/git/MJoergen/formal/wb_tdp_mem/wb_tdp_mem_bmc/engine_0/trace.vcd" 6 | [dumpfile_mtime] "Wed Feb 3 12:02:08 2021" 7 | [dumpfile_size] 3099 8 | [savefile] "/home/mike/git/MJoergen/formal/wb_tdp_mem/wb_tdp_mem.gtkw" 9 | [timestart] 0 10 | [size] 1469 824 11 | [pos] -1 -1 12 | *-2.875245 17 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 13 | [treeopen] wb_tdp_mem. 14 | [sst_width] 249 15 | [signals_width] 247 16 | [sst_expanded] 1 17 | [sst_vpaned_height] 83 18 | @28 19 | wb_tdp_mem.clk_i 20 | wb_tdp_mem.rst_i 21 | wb_tdp_mem.wb_a_cyc_i 22 | wb_tdp_mem.wb_a_stb_i 23 | wb_tdp_mem.wb_a_stall_o 24 | @22 25 | wb_tdp_mem.wb_a_addr_i[7:0] 26 | @28 27 | wb_tdp_mem.wb_a_we_i 28 | @22 29 | wb_tdp_mem.wb_a_data_i[7:0] 30 | @28 31 | wb_tdp_mem.wb_a_ack_o 32 | @22 33 | wb_tdp_mem.wb_a_data_o[7:0] 34 | @800200 35 | -tdp_ram 36 | @28 37 | wb_tdp_mem.i_tdp_ram.a_wr_en_i 38 | @22 39 | wb_tdp_mem.i_tdp_ram.a_wr_data_i[7:0] 40 | @28 41 | wb_tdp_mem.i_tdp_ram.a_rd_en_i 42 | @22 43 | wb_tdp_mem.i_tdp_ram.a_rd_data_o[7:0] 44 | wb_tdp_mem.i_tdp_ram.a_addr_i[7:0] 45 | @1000200 46 | -tdp_ram 47 | [pattern_trace] 1 48 | [pattern_trace] 0 49 | -------------------------------------------------------------------------------- /wb_tdp_mem/wb_tdp_mem.psl: -------------------------------------------------------------------------------- 1 | vunit i_wb_tdp_mem(wb_tdp_mem(synthesis)) 2 | { 3 | -- Additional signals used during formal verification 4 | signal f_count : integer range 0 to 3 := 0; 5 | 6 | 7 | -- set all declarations to run on clk_i 8 | default clock is rising_edge(clk_i); 9 | 10 | 11 | ----------------------------- 12 | -- ASSERTIONS ABOUT OUTPUTS 13 | ----------------------------- 14 | 15 | -- When wb_ack_o is de-asserted, then wb_data_o is all zeros. 16 | -- f_data_zero : assert always {not wb_a_ack_o} |-> {or(wb_a_data_o) = '0'}; 17 | 18 | -- When writing to memory, the data output is unchanged. 19 | f_data_stable : assert always {wb_a_cyc_i and wb_a_stb_i and wb_a_we_i and not rst_i} |=> {stable(wb_a_data_o)}; 20 | 21 | -- The response always appears on the exact following clock cycle, i.e. a fixed latency of 1. 22 | f_ack_next : assert always {wb_a_cyc_i and wb_a_stb_i and wb_a_we_i and not rst_i} |=> {wb_a_ack_o}; 23 | 24 | -- No ACKs allowed when the bus is idle. 25 | f_ack_idle : assert always {not (wb_a_cyc_i and wb_a_stb_i)} |=> {not wb_a_ack_o}; 26 | 27 | -- Keep track of outstanding requests 28 | p_count : process (clk_i) 29 | begin 30 | if rising_edge(clk_i) then 31 | -- Request without response 32 | if wb_a_cyc_i and wb_a_stb_i and not (wb_a_ack_o) then 33 | f_count <= f_count + 1; 34 | end if; 35 | 36 | -- Reponse without request 37 | if not(wb_a_cyc_i and wb_a_stb_i) and wb_a_ack_o then 38 | f_count <= f_count - 1; 39 | end if; 40 | 41 | if rst_i or not wb_a_cyc_i then 42 | f_count <= 0; 43 | end if; 44 | end if; 45 | end process p_count; 46 | 47 | -- At most one outstanding request 48 | f_outstanding : assert always {0 <= f_count and f_count <= 1}; 49 | 50 | -- No ACK without outstanding request 51 | f_count_0 : assert always {f_count = 0 and rst_i = '0'} |-> {not wb_a_ack_o}; 52 | 53 | -- ACK always comes immediately after an outstanding request 54 | f_count_1 : assert always {f_count = 1} |-> {wb_a_ack_o}; 55 | 56 | -- Low CYC aborts all transactions 57 | f_idle : assert always {not wb_a_cyc_i} |=> {f_count = 0}; 58 | 59 | 60 | ----------------------------- 61 | -- ASSUMPTIONS ABOUT INPUTS 62 | ----------------------------- 63 | 64 | -- Require reset at startup. 65 | f_reset : assume {rst_i}; 66 | 67 | 68 | -------------------------------------------- 69 | -- COVER STATEMENTS TO VERIFY REACHABILITY 70 | -------------------------------------------- 71 | 72 | -- Make sure memory can respond to a request 73 | f_full_to_empty : cover {f_count = 1; f_count = 0}; 74 | 75 | } -- vunit i_wb_mem(wb_mem(synthesis)) 76 | 77 | -------------------------------------------------------------------------------- /wb_tdp_mem/wb_tdp_mem.sby: -------------------------------------------------------------------------------- 1 | [tasks] 2 | bmc 3 | cover 4 | prove 5 | 6 | [options] 7 | bmc: mode bmc 8 | bmc: depth 10 9 | cover: mode cover 10 | prove: mode prove 11 | prove: depth 4 12 | 13 | [engines] 14 | smtbmc 15 | 16 | [script] 17 | ghdl --std=08 -frelaxed wb_tdp_mem.vhd wb_tdp_mem.psl tdp_ram.vhd -e wb_tdp_mem 18 | prep -top wb_tdp_mem 19 | 20 | [files] 21 | tdp_ram.vhd 22 | wb_tdp_mem.vhd 23 | wb_tdp_mem.psl 24 | 25 | -------------------------------------------------------------------------------- /wb_tdp_mem/wb_tdp_mem.vhd: -------------------------------------------------------------------------------- 1 | library ieee; 2 | use ieee.std_logic_1164.all; 3 | use ieee.numeric_std.all; 4 | use std.textio.all; 5 | 6 | -- A True Dual Port memory with two Wishbone Slave interfaces. 7 | 8 | entity wb_tdp_mem is 9 | generic ( 10 | G_INIT_FILE : string := ""; 11 | G_RAM_STYLE : string := "block"; 12 | G_ADDR_SIZE : integer := 8; 13 | G_DATA_SIZE : integer := 8 14 | ); 15 | port ( 16 | clk_i : in std_logic; 17 | rst_i : in std_logic; 18 | -- Port A 19 | wb_a_cyc_i : in std_logic; 20 | wb_a_stall_o : out std_logic; 21 | wb_a_stb_i : in std_logic; 22 | wb_a_ack_o : out std_logic; 23 | wb_a_we_i : in std_logic; 24 | wb_a_addr_i : in std_logic_vector(G_ADDR_SIZE-1 downto 0); 25 | wb_a_data_i : in std_logic_vector(G_DATA_SIZE-1 downto 0); 26 | wb_a_data_o : out std_logic_vector(G_DATA_SIZE-1 downto 0); 27 | -- Port B 28 | wb_b_cyc_i : in std_logic; 29 | wb_b_stall_o : out std_logic; 30 | wb_b_stb_i : in std_logic; 31 | wb_b_ack_o : out std_logic; 32 | wb_b_we_i : in std_logic; 33 | wb_b_addr_i : in std_logic_vector(G_ADDR_SIZE-1 downto 0); 34 | wb_b_data_i : in std_logic_vector(G_DATA_SIZE-1 downto 0); 35 | wb_b_data_o : out std_logic_vector(G_DATA_SIZE-1 downto 0) 36 | ); 37 | end entity wb_tdp_mem; 38 | 39 | architecture synthesis of wb_tdp_mem is 40 | 41 | -- Port A 42 | signal a_addr : std_logic_vector(G_ADDR_SIZE-1 downto 0); 43 | signal a_wr_en : std_logic; 44 | signal a_wr_data : std_logic_vector(G_DATA_SIZE-1 downto 0); 45 | signal a_rd_en : std_logic; 46 | signal a_rd_data : std_logic_vector(G_DATA_SIZE-1 downto 0); 47 | signal wb_a_ack : std_logic; 48 | 49 | -- Port B 50 | signal b_addr : std_logic_vector(G_ADDR_SIZE-1 downto 0); 51 | signal b_wr_en : std_logic; 52 | signal b_wr_data : std_logic_vector(G_DATA_SIZE-1 downto 0); 53 | signal b_rd_en : std_logic; 54 | signal b_rd_data : std_logic_vector(G_DATA_SIZE-1 downto 0); 55 | signal wb_b_ack : std_logic; 56 | 57 | begin 58 | 59 | i_tdp_ram : entity work.tdp_ram 60 | generic map ( 61 | G_INIT_FILE => G_INIT_FILE, 62 | G_RAM_STYLE => G_RAM_STYLE, 63 | G_ADDR_SIZE => G_ADDR_SIZE, 64 | G_DATA_SIZE => G_DATA_SIZE 65 | ) 66 | port map ( 67 | clk_i => clk_i, 68 | rst_i => rst_i, 69 | a_addr_i => a_addr, 70 | a_wr_en_i => a_wr_en, 71 | a_wr_data_i => a_wr_data, 72 | a_rd_en_i => a_rd_en, 73 | a_rd_data_o => a_rd_data, 74 | b_addr_i => b_addr, 75 | b_wr_en_i => b_wr_en, 76 | b_wr_data_i => b_wr_data, 77 | b_rd_en_i => b_rd_en, 78 | b_rd_data_o => b_rd_data 79 | ); -- i_tdp_ram 80 | 81 | 82 | -- Acknowledge 83 | p_ack : process (clk_i) 84 | begin 85 | if rising_edge(clk_i) then 86 | wb_a_ack <= wb_a_cyc_i and wb_a_stb_i and not wb_a_stall_o; 87 | wb_b_ack <= wb_b_cyc_i and wb_b_stb_i and not wb_b_stall_o; 88 | 89 | if rst_i = '1' then 90 | wb_a_ack <= '0'; 91 | wb_b_ack <= '0'; 92 | end if; 93 | end if; 94 | end process p_ack; 95 | 96 | 97 | a_wr_en <= wb_a_cyc_i and wb_a_stb_i and wb_a_we_i and not wb_a_stall_o; 98 | a_rd_en <= wb_a_cyc_i and wb_a_stb_i and (not wb_a_we_i) and not wb_a_stall_o; 99 | a_wr_data <= wb_a_data_i; 100 | a_addr <= wb_a_addr_i; 101 | wb_a_data_o <= a_rd_data; 102 | wb_a_stall_o <= '0'; 103 | wb_a_ack_o <= wb_a_ack; 104 | 105 | b_wr_en <= wb_b_cyc_i and wb_b_stb_i and wb_b_we_i and not wb_b_stall_o; 106 | b_rd_en <= wb_b_cyc_i and wb_b_stb_i and (not wb_b_we_i) and not wb_b_stall_o; 107 | b_wr_data <= wb_b_data_i; 108 | b_addr <= wb_b_addr_i; 109 | wb_b_data_o <= b_rd_data; 110 | wb_b_stall_o <= '0'; 111 | wb_b_ack_o <= wb_b_ack; 112 | 113 | end architecture synthesis; 114 | 115 | --------------------------------------------------------------------------------