Написал примерчик на верилоге для начинающих - стековый калькулятор
panchul — 24.04.2012 Господа! Как вы знаете, я вместе с другими товарищами консультирую Антона Моисеева и Андрея Маклакова, сотрудников кафедры прикладной математики Нижегородского государственного технического университета ( http://www.nntu.ru ), которые создают экспериментальный курс основ проектирования электроники для программистов.Журнал Антона Моисеева, специально созданный для освещения темы - http://1i7.livejournal.com
Мои посты по теме - http://panchul.livejournal.com/tag/nntu
Студенты Антона Моисеева уже поигрались с микросхемами малой степени интеграции, что в свое время вызвало негодование ЖЖ-юзера uzhas_sovka. Ужас наверное сам бы с удовольствием потыкал проводками в макетную плату, но после учебы в пижонском университете Чикаго uzhas_sovka боится, что его застанут за этим занятием нобелевские лауреаты по экономике и выдавят из своего круга, чисто из-за зависти.
После игрищ с микросхемами серии 4000, студенты Моисеева пересели на Xilinx FPGA, научились описывать простейшие комбинаторные цепи на Верилоге и познакомились с концепциями RS-триггера и D-триггера. Пора задизайнить что-нибудь полезное.
Фото справа - спутник, внутри которого стоит Xilinx FPGA, взято с http://www.eetimes.com/design/military-aerospace-design/4216480/High-performance-FPGAs-take-flight-in-microsatellites
По этому поводу я написал примерчик стекового калькулятора и имплементировал его в плате Digilent Basys 2 c Xilinx Spartan 3E. Примерчик иллюстрирует модульность, конечные автоматы, регистровые файлы в Верилоге, а также содержит всякие полезные штучки для начинающих пользователей FPGA - clock divider, button debouncer, 7-segment display driver.
Калькуратор работает по принципу микрокалькулятора МК-54, который помнят советские школьники физматшкол 1980-х. Имеется стек, в котором хранятся числа. 8-битные числа вводятся с помощью рычажков, которые ненавидит ЖЖ-юзер exler. Текущая верхушка стека светится как 16-битное шестнадцатеричное число. Имеются четыре круглые кнопочки - reset, enter, add (+), multiply (*).
reset все обнуляет
enter вводит 8-битное число с рычажков в верхушку стека
add (+) удаляет два числа из верхушки стека, складывает их и вставляет в стек результат
multiply (*) удаляет два числа из верхушки стека, умножает их и вставляет в стек результат
Видите рычажки на плате справа? Это те самые, которых боится Экслер. А FPGA на этой же плате - той же компании, что и FPGA в британском спутнике.
Имеются две версии калькулятора - одна (calculator) с более-менее правильным стилем, принятым у RTL-дизайнеров, другая (calculator_behavioral) - с стилем, которым хардверные люди как правило не пишут, но который более понятен софтверным людям - код выглядит как последовательная программа, описывающая поведение дизайна во время одного цикла синхросигнала.
Причин, по которым хардверные люди не пишут как в calculator_behavioral, несколько, в частности:
1. В таком коде трудно понять, какой из верилоговских регистров в процессе синтеза превратится в D-триггер, а какой просто становится проводом
2. В таком коде трудно понять, как оптимизировать тайминг
3. Такой стиль может порождать трудно-отлаживаемые ошибки из категории race conditions если человек, который его пишет не очень аккуратен с blocking и non-blocking assignmnets.
Поэтому хардвер-дизайнеры пишут как в calculator - так чтобы в частности в комбинаторном always-блоке не возникало никаких защелок (об этом сигнализирует synthesis tool), а все D-триггеры появлялись явной форме, как переменные, которым присваивается значение в небольшом блоке always @(posedge clock), который не содержит вычисления никаких формул.
Еще у чисто софтверных людей может возникнуть вопрос, почему я для стека сдвигаю весь массив, а не просто указатель на верхушку стека. Это, братцы, вся фишка хардвера - параллельный сдвиг не очень большого массива флип-флопов дешевле, чем гиганский мультиплексор для его индексации.
Мы на эти темы еще поговорим, а пока я выкладываю все файлы своего примерчика, сопровождая их сиськами Mirelle A по наводке kpt_flint для выработки положительных ассоциаций у всех читателей моего журнала мужского пола. А Наталия Радулова может получать положительные ассоциации от созерцания моего юзерпика (она когда-то писала, но потом стерла, что я доставляю ей удовольствие).
При использовании нетривиального количества моего кода ссылка на меня (Юрий Панчул) обязательна. Понятно, что я не буду карать презрением за какой-то дурацкий дебаунсер на 10 строк (дебаунсеры уже давно общие, я его сам где-то спер), но меня не устроит, если какая-нибудь Дарья Донцова спионерит мой примерчик целиком, а потом ко мне будут прибегать идиоты и настаивать, что это я якобы спионерил у Дарьи Донцовой.
//-------------------------------------------------------------------------// // // calculator.v // //-------------------------------------------------------------------------// module calculator ( input clock, input reset, input enter, input add, input multiply, input [ 7:0] data, output [15:0] result, output overflow, output [ 3:0] error ); assign error = 0; reg [15:0] alu_a; reg [15:0] alu_b; reg alu_multiply; wire [15:0] alu_result; wire alu_overflow; alu alu ( .a ( alu_a ), .b ( alu_b ), .multiply ( alu_multiply ), .result ( alu_result ), .overflow ( alu_overflow ) ); reg r_overflow; assign overflow = r_overflow; always @(posedge clock) begin if (reset) r_overflow <= 0; else r_overflow <= alu_overflow; end reg stack_push; reg stack_pop; reg [15:0] stack_write_data; wire [15:0] stack_read_data; stack stack ( .clock ( clock ), .reset ( reset ), .push ( stack_push ), .pop ( stack_pop ), .write_data ( stack_write_data ), .read_data ( stack_read_data ) ); assign result = stack_read_data; reg [15:0] r_alu_a; reg [15:0] r_alu_b; reg r_alu_multiply; reg [ 1:0] state; reg [ 1:0] next_state; always @(*) begin alu_a = r_alu_a; alu_b = r_alu_b; alu_multiply = r_alu_multiply; stack_push = 0; stack_pop = 0; stack_write_data = data; next_state = state; case (state) 0: if (enter) begin stack_push = 1; stack_write_data = data; end else if (add | multiply) begin alu_a = stack_read_data; alu_multiply = multiply; stack_pop = 1; next_state = 1; end 1: begin alu_b = stack_read_data; stack_pop = 1; next_state = 2; end 2: begin stack_push = 1; stack_write_data = alu_result; next_state = 0; end endcase end always @(posedge clock) begin if (reset) begin r_alu_a <= 0; r_alu_b <= 0; r_alu_multiply <= 0; state <= 0; end else begin r_alu_a <= alu_a; r_alu_b <= alu_b; r_alu_multiply <= alu_multiply; state <= next_state; end end endmodule
//-------------------------------------------------------------------------// // // calculator_behavioral.v // //-------------------------------------------------------------------------// `include "defines.vh" module calculator_behavioral ( input clock, input reset, input enter, input add, input multiply, input [ 7:0] data, output reg [15:0] result, output reg overflow, output reg [ 3:0] error ); reg [`stack_pointer_size - 1:0] sp; reg [15:0] stack [0 : `stack_size - 1]; reg empty; reg [16:0] result_17; reg [31:0] result_32; integer i; // This software-like style is not a good RTL style because: // // 1. It mixes blocking and non-blocking assignments // // 2. It is difficult to figure out whether a reg // is going to become a flip-flop or a wire // // 3. It makes timing optimization difficult // // However this style of coding is easier to read for a software // or a verification person always @(posedge clock or posedge reset) begin if (reset) begin sp = 0; empty = 1; result <= 0; overflow <= 0; error <= 0; end else begin if (enter) begin if (sp == `stack_size - 1) begin error <= 1; end else begin if (! empty) // Вокруг плохой стиль для RTL - смешивается blocking и non-blocking, sp = sp + 1; // неочевидно, какой регистр превратится в D-триггер, // а какой нет; трудно оптимизировать тайминг. empty = 0; for (i = `stack_size - 1; i >= 1; i = i - 1) stack [i] = stack [i - 1]; stack [0] = data; end end else if (add) begin if (sp == 0) begin error <= 2; end else begin sp = sp - 1; result_17 = stack [0] + stack [1]; stack [0] = result_17 [15:0]; overflow <= result_17 [16]; for (i = 1; i <= `stack_size - 2; i = i + 1) stack [i] = stack [i + 1]; end end else if (multiply) begin if (sp == 0) begin error <= 3; end else begin sp = sp - 1; result_32 = stack [0] * stack [1]; stack [0] = result_32 [15:0]; overflow <= | result_32 [31:16]; for (i = 1; i <= `stack_size - 2; i = i + 1) stack [i] = stack [i + 1]; end end result <= empty ? 0 : stack [0]; end end endmodule //-------------------------------------------------------------------------// // // defines.vh // //-------------------------------------------------------------------------// `ifdef word_width `else `define word_width 16 `endif `ifdef stack_size `else `define stack_size 4 `endif `ifdef stack_pointer_size `else `define stack_pointer_size 2 `endif //-------------------------------------------------------------------------// // // alu.v // //-------------------------------------------------------------------------// module alu ( input [15:0] a, input [15:0] b, input multiply, output [15:0] result, output overflow ); wire [16:0] result_add = a + b; wire [31:0] result_mul = a * b; assign result = multiply ? result_mul [15: 0] : result_add [15:0]; assign overflow = multiply ? | result_mul [31:16] : result_add [16]; endmodule //-------------------------------------------------------------------------// // // stack.v // //-------------------------------------------------------------------------// `include "defines.vh" module stack ( input clock, input reset, input push, input pop, input [`word_width - 1:0] write_data, output [`word_width - 1:0] read_data ); reg [`word_width - 1:0] stack [0:`stack_size - 1]; assign read_data = stack [0]; integer i; always @(posedge clock) begin if (reset) begin for (i = 0; i < `stack_size; i = i + 1) stack [i] <= 0; end else if (push) begin for (i = 0; i < `stack_size - 1; i = i + 1) stack [i + 1] <= stack [i]; stack [0] <= write_data; end else if (pop) begin for (i = 0; i < `stack_size - 1; i = i + 1) stack [i] <= stack [i + 1]; stack [`stack_size - 1] <= 0; end end endmodule //-------------------------------------------------------------------------// // // chip.ucf // //-------------------------------------------------------------------------// NET "mclk" LOC = "B8"; # Bank = 0, Signal name = MCLK NET "mclk" CLOCK_DEDICATED_ROUTE = FALSE; NET "mclk" PERIOD = 20 ns HIGH 50%; #TIMESPEC TS01 = FROM : FFS : TO : FFS : 20 ns; #TIMESPEC TS02 = FROM : RAMS : TO : FFS : 20 ns; #TIMESPEC TS03 = FROM : FFS : TO : RAMS : 20 ns; #TIMESPEC TS04 = FROM : RAMS : TO : RAMS : 20 ns; #TIMESPEC TS05 = FROM : FFS : TO : PADS : 20 ns; #TIMESPEC TS06 = FROM : PADS : TO : FFS : 20 ns; #TIMESPEC TS07 = FROM : PADS : TO : RAMS : 20 ns; # Pin assignment for DispCtl # Connected to Basys2 onBoard 7seg display NET "seg<0>" LOC = "L14"; # Bank = 1, Signal name = CA NET "seg<1>" LOC = "H12"; # Bank = 1, Signal name = CB NET "seg<2>" LOC = "N14"; # Bank = 1, Signal name = CC NET "seg<3>" LOC = "N11"; # Bank = 2, Signal name = CD NET "seg<4>" LOC = "P12"; # Bank = 2, Signal name = CE NET "seg<5>" LOC = "L13"; # Bank = 1, Signal name = CF NET "seg<6>" LOC = "M12"; # Bank = 1, Signal name = CG NET "dp" LOC = "N13"; # Bank = 1, Signal name = DP NET "an<3>" LOC = "K14"; # Bank = 1, Signal name = AN3 NET "an<2>" LOC = "M13"; # Bank = 1, Signal name = AN2 NET "an<1>" LOC = "J12"; # Bank = 1, Signal name = AN1 NET "an<0>" LOC = "F12"; # Bank = 1, Signal name = AN0 # Pin assignment for LEDs NET "Led<7>" LOC = "G1" ; # Bank = 3, Signal name = LD7 NET "Led<6>" LOC = "P4" ; # Bank = 2, Signal name = LD6 NET "Led<5>" LOC = "N4" ; # Bank = 2, Signal name = LD5 NET "Led<4>" LOC = "N5" ; # Bank = 2, Signal name = LD4 NET "Led<3>" LOC = "P6" ; # Bank = 2, Signal name = LD3 NET "Led<2>" LOC = "P7" ; # Bank = 3, Signal name = LD2 NET "Led<1>" LOC = "M11" ; # Bank = 2, Signal name = LD1 NET "Led<0>" LOC = "M5" ; # Bank = 2, Signal name = LD0 # Pin assignment for SWs NET "sw<7>" LOC = "N3"; # Bank = 2, Signal name = SW7 NET "sw<6>" LOC = "E2"; # Bank = 3, Signal name = SW6 NET "sw<5>" LOC = "F3"; # Bank = 3, Signal name = SW5 NET "sw<4>" LOC = "G3"; # Bank = 3, Signal name = SW4 NET "sw<3>" LOC = "B4"; # Bank = 3, Signal name = SW3 NET "sw<2>" LOC = "K3"; # Bank = 3, Signal name = SW2 NET "sw<1>" LOC = "L3"; # Bank = 3, Signal name = SW1 NET "sw<0>" LOC = "P11"; # Bank = 2, Signal name = SW0 NET "btn<3>" LOC = "A7"; # Bank = 1, Signal name = BTN3 NET "btn<2>" LOC = "M4"; # Bank = 0, Signal name = BTN2 NET "btn<1>" LOC = "C11"; # Bank = 2, Signal name = BTN1 NET "btn<0>" LOC = "G12"; # Bank = 0, Signal name = BTN0 //-------------------------------------------------------------------------// // // chip.v // //-------------------------------------------------------------------------// module chip ( input mclk, output [6:0] seg, output dp, output [3:0] an, output [7:0] Led, input [7:0] sw, input [3:0] btn ); system system ( .clock ( mclk ), .switches ( sw ), .buttons ( btn ), .leds ( Led ), .seven_segments ( seg ), .dot ( dp ), .anodes ( an ) ); endmodule //-------------------------------------------------------------------------// // // system.v // //-------------------------------------------------------------------------// module system ( input clock, input [7:0] switches, input [3:0] buttons, output reg [7:0] leds, output [6:0] seven_segments, output dot, output [3:0] anodes ); wire reset = buttons [3]; wire clock_for_debouncing; wire clock_for_display; clock_divider clock_divider ( clock, reset, clock_for_debouncing, clock_for_display ); wire enter; wire add; wire multiply; debouncer debouncer2 ( clock, clock_for_debouncing, reset, buttons [2], enter ); debouncer debouncer1 ( clock, clock_for_debouncing, reset, buttons [1], add ); debouncer debouncer0 ( clock, clock_for_debouncing, reset, buttons [0], multiply ); wire [ 7:0] data; wire [15:0] result; wire overflow; wire [ 3:0] error; assign data = switches; display display ( clock_for_display, reset, result, overflow, error, seven_segments, dot, anodes ); calculator calculator ( clock, reset, enter, add, multiply, data, result, overflow, error ); always @(posedge clock_for_display) leds <= reset ? 0 : switches; endmodule //-------------------------------------------------------------------------// // // clock_divider.v // //-------------------------------------------------------------------------// module clock_divider ( input clock, input reset, output clock_for_debouncing, output clock_for_display ); reg [19:0] counter; always @(posedge clock) begin if (reset) counter <= 0; else counter <= counter + 1; end assign clock_for_debouncing = counter [19]; assign clock_for_display = counter [15]; endmodule //-------------------------------------------------------------------------// // // debouncer.v // //-------------------------------------------------------------------------// module debouncer ( input clock, input clock_for_debouncing, input reset, input button, output reg push ); reg [2:0] samples; always @(posedge clock_for_debouncing) begin if (reset) samples <= 0; else samples <= { samples [1:0], button }; end wire current = & samples; reg previous; always @(posedge clock) begin if (reset) begin previous <= 0; push <= 0; end else begin previous <= current; push <= { previous, current } == 2'b01; end end endmodule //-------------------------------------------------------------------------// // // display.v // //-------------------------------------------------------------------------// module display ( input clock, input reset, input [15:0] number, input overflow, input [ 3:0] error, output reg [ 6:0] seven_segments, output reg dot, output reg [ 3:0] anodes ); parameter [6:0] seg_E = 'b0000110; parameter [6:0] seg_r = 'b0101111; function [6:0] bcd_to_seg (input [3:0] bcd); case (bcd) 'h0: bcd_to_seg = 'b1000000; // a b c d e f g 'h1: bcd_to_seg = 'b1111001; 'h2: bcd_to_seg = 'b0100100; // --a-- 'h3: bcd_to_seg = 'b0110000; // | | 'h4: bcd_to_seg = 'b0011001; // f b 'h5: bcd_to_seg = 'b0010010; // | | 'h6: bcd_to_seg = 'b0000010; // --g-- 'h7: bcd_to_seg = 'b1111000; // | | 'h8: bcd_to_seg = 'b0000000; // e c 'h9: bcd_to_seg = 'b0011000; // | | 'ha: bcd_to_seg = 'b0001000; // --d-- 'hb: bcd_to_seg = 'b0000011; 'hc: bcd_to_seg = 'b1000110; 'hd: bcd_to_seg = 'b0100001; 'he: bcd_to_seg = 'b0000110; 'hf: bcd_to_seg = 'b0001110; endcase endfunction reg [1:0] i; always @(posedge clock) begin if (reset) begin i <= 0; seven_segments <= bcd_to_seg (0); dot <= ~ 0; anodes <= ~ 'b1111; end else begin if (error != 0) begin case (i) 0 : seven_segments <= bcd_to_seg (error); 1, 2 : seven_segments <= seg_r; 3 : seven_segments <= seg_E; endcase dot <= ~ 0; anodes <= ~ (1 << i); end else begin seven_segments <= bcd_to_seg (number [i * 4 +: 4]); dot <= ~ (i == 0 ? overflow : 0); anodes <= ~ (1 << i); end i <= i + 1; end end endmodule //-------------------------------------------------------------------------// // // testbench.v // //-------------------------------------------------------------------------// module testbench; reg clock; reg reset; reg enter; reg add; reg multiply; reg [ 7:0] data; wire [15:0] result; wire overflow; wire [ 3:0] error; calculator calculator ( clock, reset, enter, add, multiply, data, result, overflow, error ); initial begin clock = 0; forever # 5 clock = ! clock; end //---------------------------------------------------------------- task dump; begin $write ("data=%h enter=%h add=%h multiply=%h", data, enter, add, multiply); $display (" result=%h overflow=%h error=%h", result, overflow, error); end endtask task t_reset; begin reset <= 1; repeat (10) @(posedge clock); enter <= 0; add <= 0; multiply <= 0; data <= 0; reset <= 0; repeat (10) @(posedge clock); $write ("After reset "); dump; end endtask task t_enter (input [7:0] value); begin data <= value; enter <= 1; @(posedge clock); enter <= 0; repeat (10) @(posedge clock); $write ("After enter %x ", value); dump; end endtask task t_add; begin add <= 1; @(posedge clock); add <= 0; repeat (10) @(posedge clock); $write ("After add "); dump; end endtask task t_multiply; begin multiply <= 1; @(posedge clock); multiply <= 0; repeat (10) @(posedge clock); $write ("After multiply "); dump; end endtask //---------------------------------------------------------------- initial begin $display ("******** 2 3 * 4 5 * + ********"); t_reset; t_enter (2); t_enter (3); t_multiply; t_enter (4); t_enter (5); t_multiply; t_add; $display ("******** ff ff ff * * overflow ********"); t_reset; t_enter ('hff); t_enter ('hff); t_enter ('hff); t_multiply; t_multiply; $display ("******** ff 1 + ff * ff + 1 + overflow ********"); t_reset; t_enter ('hff); t_enter ('h01); t_add; t_enter ('hff); t_multiply; t_enter ('hff); t_add; t_enter ('h01); t_add; $display ("******** 1 2 3 4 5 * * * * * ********"); t_reset; t_enter ('h01); t_enter ('h02); t_enter ('h03); t_enter ('h04); t_enter ('h05); repeat (5) t_multiply; $finish; end initial $dumpvars; endmodule
UPD: Сергей Вакуленко подошел и сказал, что в такие серьезные посты нельзя помещать женские груди. Я сказал, что грудями ЖЖ не испортишь. На том и разошлись. Думаю.
ljpromo, приди!